1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-11 18:20:26 +09:00
Satori/src/coreclr/vm/interpreter.cpp
Jan Kotas 3f7ffddeda
Move last P/Invoke error from native Thread and delete C/C++ SafeHandle implementation (#100267)
* Delete native safehandle

* Delete PInvoke last error on thread

* Delete IsRealThreadPoolResetNeeded

* Delete TS_TaskReset

* Delete GetThreadContext

* Fix build break

* Delete unused resource strings

* Introduce FEATURE_IJW and use it in number of places
2024-03-27 06:14:44 -07:00

12572 lines
421 KiB
C++

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
//
#include "common.h"
#ifdef FEATURE_INTERPRETER
#include "interpreter.h"
#include "interpreter.hpp"
#include "cgencpu.h"
#include "stublink.h"
#include "openum.h"
#include "fcall.h"
#include "frames.h"
#include "gcheaputilities.h"
#include <float.h>
#include "jitinterface.h"
#include "safemath.h"
#include "exceptmacros.h"
#include "runtimeexceptionkind.h"
#include "runtimehandles.h"
#include "vars.hpp"
#include "cycletimer.h"
inline CORINFO_CALLINFO_FLAGS combine(CORINFO_CALLINFO_FLAGS flag1, CORINFO_CALLINFO_FLAGS flag2)
{
return (CORINFO_CALLINFO_FLAGS) (flag1 | flag2);
}
static CorInfoType asCorInfoType(CORINFO_CLASS_HANDLE clsHnd)
{
TypeHandle typeHnd(clsHnd);
return CEEInfo::asCorInfoType(typeHnd.GetInternalCorElementType(), typeHnd, NULL);
}
InterpreterMethodInfo::InterpreterMethodInfo(CEEInfo* comp, CORINFO_METHOD_INFO* methInfo)
: m_method(methInfo->ftn),
m_module(methInfo->scope),
m_ILCode(methInfo->ILCode),
m_ILCodeEnd(methInfo->ILCode + methInfo->ILCodeSize),
m_maxStack(methInfo->maxStack),
#if INTERP_PROFILE
m_totIlInstructionsExeced(0),
m_maxIlInstructionsExeced(0),
#endif
m_ehClauseCount(methInfo->EHcount),
m_varArgHandleArgNum(NO_VA_ARGNUM),
m_numArgs(methInfo->args.numArgs),
m_numLocals(methInfo->locals.numArgs),
m_flags(0),
m_argDescs(NULL),
m_returnType(methInfo->args.retType),
m_invocations(0),
m_methodCache(NULL)
{
// Overflow sanity check. (Can ILCodeSize ever be zero?)
_ASSERTE(m_ILCode <= m_ILCodeEnd);
// Does the calling convention indicate an implicit "this" (first arg) or generic type context arg (last arg)?
SetFlag<Flag_hasThisArg>((methInfo->args.callConv & CORINFO_CALLCONV_HASTHIS) != 0);
if (GetFlag<Flag_hasThisArg>())
{
GCX_PREEMP();
CORINFO_CLASS_HANDLE methClass = comp->getMethodClass(methInfo->ftn);
DWORD attribs = comp->getClassAttribs(methClass);
SetFlag<Flag_thisArgIsObjPtr>((attribs & CORINFO_FLG_VALUECLASS) == 0);
}
#if INTERP_PROFILE || defined(_DEBUG)
{
const char* clsName;
#if defined(_DEBUG)
m_methName = ::eeGetMethodFullName(comp, methInfo->ftn, &clsName);
#else
m_methName = getMethodName(comp, methInfo->ftn, &clsName);
#endif
char* myClsName = new char[strlen(clsName) + 1];
strcpy(myClsName, clsName);
m_clsName = myClsName;
}
#endif // INTERP_PROFILE
// Do we have a ret buff? If its a struct or refany, then *maybe*, depending on architecture...
bool hasRetBuff = (methInfo->args.retType == CORINFO_TYPE_VALUECLASS || methInfo->args.retType == CORINFO_TYPE_REFANY);
#if defined(FEATURE_HFA)
// ... unless its an HFA type (and not varargs)...
if (hasRetBuff && (comp->getHFAType(methInfo->args.retTypeClass) != CORINFO_HFA_ELEM_NONE) && methInfo->args.getCallConv() != CORINFO_CALLCONV_VARARG)
{
hasRetBuff = false;
}
#endif
#if defined(UNIX_AMD64_ABI) || defined(HOST_LOONGARCH64) || defined(HOST_RISCV64)
// ...or it fits into two registers.
if (hasRetBuff && getClassSize(methInfo->args.retTypeClass) <= 2 * sizeof(void*))
{
hasRetBuff = false;
}
#elif defined(HOST_ARM) || defined(HOST_AMD64)|| defined(HOST_ARM64)
// ...or it fits into one register.
if (hasRetBuff && getClassSize(methInfo->args.retTypeClass) <= sizeof(void*))
{
hasRetBuff = false;
}
#endif
SetFlag<Flag_hasRetBuffArg>(hasRetBuff);
MetaSig sig(reinterpret_cast<MethodDesc*>(methInfo->ftn));
SetFlag<Flag_hasGenericsContextArg>((methInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE) != 0);
SetFlag<Flag_isVarArg>((methInfo->args.callConv & CORINFO_CALLCONV_VARARG) != 0);
SetFlag<Flag_typeHasGenericArgs>(methInfo->args.sigInst.classInstCount > 0);
SetFlag<Flag_methHasGenericArgs>(methInfo->args.sigInst.methInstCount > 0);
_ASSERTE_MSG(!GetFlag<Flag_hasGenericsContextArg>()
|| ((GetFlag<Flag_typeHasGenericArgs>() && !(GetFlag<Flag_hasThisArg>() && GetFlag<Flag_thisArgIsObjPtr>())) || GetFlag<Flag_methHasGenericArgs>()),
"If the method takes a generic parameter, is a static method of generic class (or meth of a value class), and/or itself takes generic parameters");
if (GetFlag<Flag_hasThisArg>())
{
m_numArgs++;
}
if (GetFlag<Flag_hasRetBuffArg>())
{
m_numArgs++;
}
if (GetFlag<Flag_isVarArg>())
{
m_numArgs++;
}
if (GetFlag<Flag_hasGenericsContextArg>())
{
m_numArgs++;
}
if (m_numArgs == 0)
{
m_argDescs = NULL;
}
else
{
m_argDescs = new ArgDesc[m_numArgs];
}
// Now we'll do the locals.
m_localDescs = new LocalDesc[m_numLocals];
// Allocate space for the pinning reference bits (lazily).
m_localIsPinningRefBits = NULL;
// Now look at each local.
CORINFO_ARG_LIST_HANDLE localsPtr = methInfo->locals.args;
CORINFO_CLASS_HANDLE vcTypeRet;
unsigned curLargeStructOffset = 0;
for (unsigned k = 0; k < methInfo->locals.numArgs; k++)
{
// TODO: if this optimization succeeds, the switch below on localType
// can become much simpler.
m_localDescs[k].m_offset = 0;
#ifdef _DEBUG
vcTypeRet = NULL;
#endif
CorInfoTypeWithMod localTypWithMod = comp->getArgType(&methInfo->locals, localsPtr, &vcTypeRet);
// If the local vars is a pinning reference, set the bit to indicate this.
if ((localTypWithMod & CORINFO_TYPE_MOD_PINNED) != 0)
{
SetPinningBit(k);
}
CorInfoType localType = strip(localTypWithMod);
switch (localType)
{
case CORINFO_TYPE_VALUECLASS:
case CORINFO_TYPE_REFANY: // Just a special case: vcTypeRet is handle for TypedReference in this case...
{
InterpreterType tp = InterpreterType(comp, vcTypeRet);
unsigned size = static_cast<unsigned>(tp.Size(comp));
size = max(size, sizeof(void*));
m_localDescs[k].m_type = tp;
if (tp.IsLargeStruct(comp))
{
m_localDescs[k].m_offset = curLargeStructOffset;
curLargeStructOffset += size;
}
}
break;
case CORINFO_TYPE_VAR:
NYI_INTERP("argument of generic parameter type"); // Should not happen;
break;
default:
m_localDescs[k].m_type = InterpreterType(localType);
break;
}
m_localDescs[k].m_typeStackNormal = m_localDescs[k].m_type.StackNormalize();
localsPtr = comp->getArgNext(localsPtr);
}
m_largeStructLocalSize = curLargeStructOffset;
}
void InterpreterMethodInfo::InitArgInfo(CEEInfo* comp, CORINFO_METHOD_INFO* methInfo, short* argOffsets_)
{
unsigned numSigArgsPlusThis = methInfo->args.numArgs;
if (GetFlag<Flag_hasThisArg>())
{
numSigArgsPlusThis++;
}
// The m_argDescs array is constructed in the following "canonical" order:
// 1. 'this' pointer
// 2. signature arguments
// 3. return buffer
// 4. type parameter -or- vararg cookie
//
// argOffsets_ is passed in this order, and serves to establish the offsets to arguments
// when the interpreter is invoked using the native calling convention (i.e., not directly).
//
// When the interpreter is invoked directly, the arguments will appear in the same order
// and form as arguments passed to MethodDesc::CallDescr(). This ordering is as follows:
// 1. 'this' pointer
// 2. return buffer
// 3. signature arguments
//
// MethodDesc::CallDescr() does not support generic parameters or varargs functions.
_ASSERTE_MSG((methInfo->args.callConv & (CORINFO_CALLCONV_EXPLICITTHIS)) == 0,
"Don't yet handle EXPLICITTHIS calling convention modifier.");
switch (methInfo->args.callConv & CORINFO_CALLCONV_MASK)
{
case CORINFO_CALLCONV_DEFAULT:
case CORINFO_CALLCONV_VARARG:
{
unsigned k = 0;
ARG_SLOT* directOffset = NULL;
short directRetBuffOffset = 0;
short directVarArgOffset = 0;
short directTypeParamOffset = 0;
// If there's a "this" argument, handle it.
if (GetFlag<Flag_hasThisArg>())
{
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_UNDEF);
#ifdef FEATURE_INSTANTIATINGSTUB_AS_IL
MethodDesc *pMD = reinterpret_cast<MethodDesc*>(methInfo->ftn);
// The signature of the ILStubs may be misleading.
// If a StubTarget is ever set, we'll find the correct type by inspecting the
// target, rather than the stub.
if (pMD->IsILStub())
{
if (pMD->AsDynamicMethodDesc()->IsUnboxingILStub())
{
// This is an unboxing stub where the thisptr is passed as a boxed VT.
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_CLASS);
}
else
{
MethodDesc *pTargetMD = pMD->AsDynamicMethodDesc()->GetILStubResolver()->GetStubTargetMethodDesc();
if (pTargetMD != NULL)
{
if (pTargetMD->GetMethodTable()->IsValueType())
{
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_BYREF);
}
else
{
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_CLASS);
}
}
}
}
#endif // FEATURE_INSTANTIATINGSTUB_AS_IL
if (m_argDescs[k].m_type == InterpreterType(CORINFO_TYPE_UNDEF))
{
CORINFO_CLASS_HANDLE cls = comp->getMethodClass(methInfo->ftn);
DWORD attribs = comp->getClassAttribs(cls);
if (attribs & CORINFO_FLG_VALUECLASS)
{
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_BYREF);
}
else
{
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_CLASS);
}
}
m_argDescs[k].m_typeStackNormal = m_argDescs[k].m_type;
m_argDescs[k].m_nativeOffset = argOffsets_[k];
m_argDescs[k].m_directOffset = static_cast<short>(reinterpret_cast<intptr_t>(ArgSlotEndiannessFixup(directOffset, sizeof(void*))));
directOffset++;
k++;
}
// If there is a return buffer, it will appear next in the arguments list for a direct call.
// Reserve its offset now, for use after the explicit arguments.
#if defined(HOST_ARM)
// On ARM, for direct calls we always treat HFA return types as having ret buffs.
// So figure out if we have an HFA return type.
bool hasHFARetType =
methInfo->args.retType == CORINFO_TYPE_VALUECLASS
&& (comp->getHFAType(methInfo->args.retTypeClass) != CORINFO_HFA_ELEM_NONE)
&& methInfo->args.getCallConv() != CORINFO_CALLCONV_VARARG;
#endif // defined(HOST_ARM)
if (GetFlag<Flag_hasRetBuffArg>()
#if defined(HOST_ARM)
// On ARM, for direct calls we always treat HFA return types as having ret buffs.
|| hasHFARetType
#endif // defined(HOST_ARM)
)
{
directRetBuffOffset = static_cast<short>(reinterpret_cast<intptr_t>(ArgSlotEndiannessFixup(directOffset, sizeof(void*))));
directOffset++;
}
#if defined(HOST_AMD64)
if (GetFlag<Flag_isVarArg>())
{
directVarArgOffset = static_cast<short>(reinterpret_cast<intptr_t>(ArgSlotEndiannessFixup(directOffset, sizeof(void*))));
directOffset++;
}
if (GetFlag<Flag_hasGenericsContextArg>())
{
directTypeParamOffset = static_cast<short>(reinterpret_cast<intptr_t>(ArgSlotEndiannessFixup(directOffset, sizeof(void*))));
directOffset++;
}
#endif
// Now record the argument types for the rest of the arguments.
InterpreterType it;
CORINFO_CLASS_HANDLE vcTypeRet;
CORINFO_ARG_LIST_HANDLE argPtr = methInfo->args.args;
for (; k < numSigArgsPlusThis; k++)
{
CorInfoTypeWithMod argTypWithMod = comp->getArgType(&methInfo->args, argPtr, &vcTypeRet);
CorInfoType argType = strip(argTypWithMod);
switch (argType)
{
case CORINFO_TYPE_VALUECLASS:
case CORINFO_TYPE_REFANY: // Just a special case: vcTypeRet is handle for TypedReference in this case...
it = InterpreterType(comp, vcTypeRet);
break;
default:
// Everything else is just encoded as a shifted CorInfoType.
it = InterpreterType(argType);
break;
}
m_argDescs[k].m_type = it;
m_argDescs[k].m_typeStackNormal = it.StackNormalize();
m_argDescs[k].m_nativeOffset = argOffsets_[k];
// When invoking the interpreter directly, large value types are always passed by reference.
if (it.IsLargeStruct(comp))
{
m_argDescs[k].m_directOffset = static_cast<short>(reinterpret_cast<intptr_t>(ArgSlotEndiannessFixup(directOffset, sizeof(void*))));
}
else
{
m_argDescs[k].m_directOffset = static_cast<short>(reinterpret_cast<intptr_t>(ArgSlotEndiannessFixup(directOffset, it.Size(comp))));
}
argPtr = comp->getArgNext(argPtr);
directOffset++;
}
if (GetFlag<Flag_hasRetBuffArg>())
{
// The generic type context is an unmanaged pointer (native int).
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_BYREF);
m_argDescs[k].m_typeStackNormal = m_argDescs[k].m_type;
m_argDescs[k].m_nativeOffset = argOffsets_[k];
m_argDescs[k].m_directOffset = directRetBuffOffset;
k++;
}
if (GetFlag<Flag_hasGenericsContextArg>())
{
// The vararg cookie is an unmanaged pointer (native int).
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_NATIVEINT);
m_argDescs[k].m_typeStackNormal = m_argDescs[k].m_type;
m_argDescs[k].m_nativeOffset = argOffsets_[k];
m_argDescs[k].m_directOffset = directTypeParamOffset;
directOffset++;
k++;
}
if (GetFlag<Flag_isVarArg>())
{
// The generic type context is an unmanaged pointer (native int).
m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_NATIVEINT);
m_argDescs[k].m_typeStackNormal = m_argDescs[k].m_type;
m_argDescs[k].m_nativeOffset = argOffsets_[k];
m_argDescs[k].m_directOffset = directVarArgOffset;
k++;
}
}
break;
case IMAGE_CEE_CS_CALLCONV_C:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- IMAGE_CEE_CS_CALLCONV_C");
break;
case IMAGE_CEE_CS_CALLCONV_STDCALL:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- IMAGE_CEE_CS_CALLCONV_STDCALL");
break;
case IMAGE_CEE_CS_CALLCONV_THISCALL:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- IMAGE_CEE_CS_CALLCONV_THISCALL");
break;
case IMAGE_CEE_CS_CALLCONV_FASTCALL:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- IMAGE_CEE_CS_CALLCONV_FASTCALL");
break;
case CORINFO_CALLCONV_FIELD:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_FIELD");
break;
case CORINFO_CALLCONV_LOCAL_SIG:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_LOCAL_SIG");
break;
case CORINFO_CALLCONV_PROPERTY:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_PROPERTY");
break;
case CORINFO_CALLCONV_UNMANAGED:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_UNMANAGED");
break;
case CORINFO_CALLCONV_NATIVEVARARG:
NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_NATIVEVARARG");
break;
default:
_ASSERTE_ALL_BUILDS(false); // shouldn't get here
}
}
InterpreterMethodInfo::~InterpreterMethodInfo()
{
if (m_methodCache != NULL)
{
delete reinterpret_cast<ILOffsetToItemCache*>(m_methodCache);
}
}
void InterpreterMethodInfo::AllocPinningBitsIfNeeded()
{
if (m_localIsPinningRefBits != NULL)
return;
unsigned numChars = (m_numLocals + 7) / 8;
m_localIsPinningRefBits = new char[numChars];
for (unsigned i = 0; i < numChars; i++)
{
m_localIsPinningRefBits[i] = char(0);
}
}
void InterpreterMethodInfo::SetPinningBit(unsigned locNum)
{
_ASSERTE_MSG(locNum < m_numLocals, "Precondition");
AllocPinningBitsIfNeeded();
unsigned ind = locNum / 8;
unsigned bitNum = locNum - (ind * 8);
m_localIsPinningRefBits[ind] |= (1 << bitNum);
}
bool InterpreterMethodInfo::GetPinningBit(unsigned locNum)
{
_ASSERTE_MSG(locNum < m_numLocals, "Precondition");
if (m_localIsPinningRefBits == NULL)
return false;
unsigned ind = locNum / 8;
unsigned bitNum = locNum - (ind * 8);
return (m_localIsPinningRefBits[ind] & (1 << bitNum)) != 0;
}
void Interpreter::ArgState::AddArg(unsigned canonIndex, short numSlots, bool noReg, bool twoSlotAlign)
{
#if defined(HOST_AMD64)
_ASSERTE(!noReg);
_ASSERTE(!twoSlotAlign);
AddArgAmd64(canonIndex, numSlots, /*isFloatingType*/false);
return;
#else // !HOST_AMD64
#if defined(HOST_X86) || defined(HOST_ARM64)
_ASSERTE(!twoSlotAlign); // Shouldn't use this flag on x86 (it wouldn't work right in the stack, at least).
#endif
// If the argument requires two-slot alignment, make sure we have it. This is the
// ARM model: both in regs and on the stack.
if (twoSlotAlign)
{
if (!noReg && numRegArgs < NumberOfIntegerRegArgs())
{
if ((numRegArgs % 2) != 0)
{
numRegArgs++;
}
}
else
{
if ((callerArgStackSlots % 2) != 0)
{
callerArgStackSlots++;
}
}
}
#if defined(HOST_ARM64)
// On ARM64 we're not going to place an argument 'partially' on the stack
// if all slots fits into registers, they go into registers, otherwise they go into stack.
if (!noReg && numRegArgs+numSlots <= NumberOfIntegerRegArgs())
#else
if (!noReg && numRegArgs < NumberOfIntegerRegArgs())
#endif
{
argIsReg[canonIndex] = ARS_IntReg;
argOffsets[canonIndex] = numRegArgs * sizeof(void*);
numRegArgs += numSlots;
// If we overflowed the regs, we consume some stack arg space.
if (numRegArgs > NumberOfIntegerRegArgs())
{
callerArgStackSlots += (numRegArgs - NumberOfIntegerRegArgs());
}
}
else
{
#if defined(HOST_X86)
// On X86, stack args are pushed in order. We will add the total size of the arguments to this offset,
// so we set this to a negative number relative to the SP before the first arg push.
callerArgStackSlots += numSlots;
ClrSafeInt<short> offset(-callerArgStackSlots);
#elif defined(HOST_ARM) || defined(HOST_ARM64)
// On ARM, args are pushed in *reverse* order. So we will create an offset relative to the address
// of the first stack arg; later, we will add the size of the non-stack arguments.
ClrSafeInt<short> offset(callerArgStackSlots);
#elif defined(HOST_LOONGARCH64)
callerArgStackSlots += numSlots;
ClrSafeInt<short> offset(-callerArgStackSlots);
#elif defined(HOST_RISCV64)
callerArgStackSlots += numSlots;
ClrSafeInt<short> offset(-callerArgStackSlots);
#endif
offset *= static_cast<short>(sizeof(void*));
_ASSERTE(!offset.IsOverflow());
argOffsets[canonIndex] = offset.Value();
#if defined(HOST_ARM) || defined(HOST_ARM64)
callerArgStackSlots += numSlots;
#endif
}
#endif // !HOST_AMD64
}
#if defined(HOST_AMD64)
#if defined(UNIX_AMD64_ABI)
void Interpreter::ArgState::AddArgAmd64(unsigned canonIndex, unsigned short numSlots, bool isFloatingType)
{
int regSlots = numFPRegArgSlots + numRegArgs;
if (isFloatingType && numFPRegArgSlots + 1 < MaxNumFPRegArgSlots)
{
_ASSERTE(numSlots == 1);
argIsReg[canonIndex] = ARS_FloatReg;
argOffsets[canonIndex] = regSlots * sizeof(void*);
fpArgsUsed |= (0x1 << regSlots);
numFPRegArgSlots += 1;
return;
}
else if (numSlots < 3 && (numRegArgs + numSlots <= NumberOfIntegerRegArgs()))
{
argIsReg[canonIndex] = ARS_IntReg;
argOffsets[canonIndex] = regSlots * sizeof(void*);
numRegArgs += numSlots;
}
else
{
argIsReg[canonIndex] = ARS_NotReg;
ClrSafeInt<short> offset(callerArgStackSlots * sizeof(void*));
_ASSERTE(!offset.IsOverflow());
argOffsets[canonIndex] = offset.Value();
callerArgStackSlots += numSlots;
}
}
#else
// Windows AMD64 calling convention allows any type that can be contained in 64 bits to be passed in registers,
// if not contained or they are of a size not a power of 2, then they are passed by reference on the stack.
// RCX, RDX, R8, R9 are the int arg registers. XMM0-3 overlap with the integer registers and are used
// for floating point arguments.
void Interpreter::ArgState::AddArgAmd64(unsigned canonIndex, unsigned short numSlots, bool isFloatingType)
{
// If floating type and there are slots use a float reg slot.
if (isFloatingType && (numFPRegArgSlots < MaxNumFPRegArgSlots))
{
_ASSERTE(numSlots == 1);
argIsReg[canonIndex] = ARS_FloatReg;
argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*);
fpArgsUsed |= (0x1 << (numFPRegArgSlots + 1));
numFPRegArgSlots += 1;
numRegArgs += 1; // Increment int reg count due to shadowing.
return;
}
// If we have an integer/aligned-struct arg or a reference of a struct that got copied on
// to the stack, it would go into a register or a stack slot.
if (numRegArgs != NumberOfIntegerRegArgs())
{
argIsReg[canonIndex] = ARS_IntReg;
argOffsets[canonIndex] = numRegArgs * sizeof(void*);
numRegArgs += 1;
numFPRegArgSlots += 1; // Increment FP reg count due to shadowing.
}
else
{
argIsReg[canonIndex] = ARS_NotReg;
ClrSafeInt<short> offset(callerArgStackSlots * sizeof(void*));
_ASSERTE(!offset.IsOverflow());
argOffsets[canonIndex] = offset.Value();
callerArgStackSlots += 1;
}
}
#endif //UNIX_AMD64_ABI
#endif
void Interpreter::ArgState::AddFPArg(unsigned canonIndex, unsigned short numSlots, bool twoSlotAlign)
{
#if defined(HOST_AMD64)
_ASSERTE(!twoSlotAlign);
_ASSERTE(numSlots == 1);
AddArgAmd64(canonIndex, numSlots, /*isFloatingType*/ true);
#elif defined(HOST_X86)
_ASSERTE(false); // Don't call this on x86; we pass all FP on the stack.
#elif defined(HOST_ARM)
// We require "numSlots" alignment.
_ASSERTE(numFPRegArgSlots + numSlots <= MaxNumFPRegArgSlots);
argIsReg[canonIndex] = ARS_FloatReg;
if (twoSlotAlign)
{
// If we require two slot alignment, the number of slots must be a multiple of two.
_ASSERTE((numSlots % 2) == 0);
// Skip a slot if necessary.
if ((numFPRegArgSlots % 2) != 0)
{
numFPRegArgSlots++;
}
// We always use new slots for two slot aligned args precision...
argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*);
for (unsigned short i = 0; i < numSlots/2; i++)
{
fpArgsUsed |= (0x3 << (numFPRegArgSlots + i));
}
numFPRegArgSlots += numSlots;
}
else
{
if (numSlots == 1)
{
// A single-precision (float) argument. We must do "back-filling" where possible, searching
// for previous unused registers.
unsigned slot = 0;
while (slot < 32 && (fpArgsUsed & (1 << slot))) slot++;
_ASSERTE(slot < 32); // Search succeeded.
_ASSERTE(slot <= numFPRegArgSlots); // No bits at or above numFPRegArgSlots are set (regs used).
argOffsets[canonIndex] = slot * sizeof(void*);
fpArgsUsed |= (0x1 << slot);
if (slot == numFPRegArgSlots)
numFPRegArgSlots += numSlots;
}
else
{
// We can always allocate at after the last used slot.
argOffsets[numFPRegArgSlots] = numFPRegArgSlots * sizeof(void*);
for (unsigned i = 0; i < numSlots; i++)
{
fpArgsUsed |= (0x1 << (numFPRegArgSlots + i));
}
numFPRegArgSlots += numSlots;
}
}
#elif defined(HOST_ARM64)
_ASSERTE(numFPRegArgSlots + numSlots <= MaxNumFPRegArgSlots);
_ASSERTE(!twoSlotAlign);
argIsReg[canonIndex] = ARS_FloatReg;
argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*);
for (unsigned i = 0; i < numSlots; i++)
{
fpArgsUsed |= (0x1 << (numFPRegArgSlots + i));
}
numFPRegArgSlots += numSlots;
#elif defined(HOST_LOONGARCH64)
assert(numFPRegArgSlots + numSlots <= MaxNumFPRegArgSlots);
assert(!twoSlotAlign);
argIsReg[canonIndex] = ARS_FloatReg;
argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*);
for (unsigned i = 0; i < numSlots; i++)
{
fpArgsUsed |= (0x1 << (numFPRegArgSlots + i));
}
numFPRegArgSlots += numSlots;
#elif defined(HOST_RISCV64)
assert(numFPRegArgSlots + numSlots <= MaxNumFPRegArgSlots);
assert(!twoSlotAlign);
argIsReg[canonIndex] = ARS_FloatReg;
argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*);
for (unsigned i = 0; i < numSlots; i++)
{
fpArgsUsed |= (0x1 << (numFPRegArgSlots + i));
}
numFPRegArgSlots += numSlots;
#else
#error "Unsupported architecture"
#endif
}
// static
CorJitResult Interpreter::GenerateInterpreterStub(CEEInfo* comp,
CORINFO_METHOD_INFO* info,
/*OUT*/ BYTE **nativeEntry,
/*OUT*/ ULONG *nativeSizeOfCode,
InterpreterMethodInfo** ppInterpMethodInfo,
bool jmpCall)
{
//
// First, ensure that the compiler-specific statics are initialized.
//
InitializeCompilerStatics(comp);
//
// Next, use switches and IL scanning to determine whether to interpret this method.
//
#if INTERP_TRACING
#define TRACE_SKIPPED(cls, meth, reason) \
if (s_DumpInterpreterStubsFlag.val(CLRConfig::INTERNAL_DumpInterpreterStubs)) { \
fprintf(GetLogFile(), "Skipping %s:%s (%s).\n", cls, meth, reason); \
}
#else
#define TRACE_SKIPPED(cls, meth, reason)
#endif
// If jmpCall, we only need to do computations involving method info.
if (!jmpCall)
{
const char* clsName;
const char* methName = getMethodName(comp, info->ftn, &clsName);
if ( !s_InterpretMeths.contains(methName, clsName, info->args.pSig)
|| s_InterpretMethsExclude.contains(methName, clsName, info->args.pSig))
{
TRACE_SKIPPED(clsName, methName, "not in set of methods to interpret");
return CORJIT_SKIPPED;
}
unsigned methHash = comp->getMethodHash(info->ftn);
if ( methHash < s_InterpretMethHashMin.val(CLRConfig::INTERNAL_InterpreterMethHashMin)
|| methHash > s_InterpretMethHashMax.val(CLRConfig::INTERNAL_InterpreterMethHashMax))
{
TRACE_SKIPPED(clsName, methName, "hash not within range to interpret");
return CORJIT_SKIPPED;
}
MethodDesc* pMD = reinterpret_cast<MethodDesc*>(info->ftn);
#if !INTERP_ILSTUBS
if (pMD->IsILStub())
{
TRACE_SKIPPED(clsName, methName, "interop stubs not supported");
return CORJIT_SKIPPED;
}
else
#endif // !INTERP_ILSTUBS
if (!s_InterpreterDoLoopMethods && MethodMayHaveLoop(info->ILCode, info->ILCodeSize))
{
TRACE_SKIPPED(clsName, methName, "has loop, not interpreting loop methods.");
return CORJIT_SKIPPED;
}
s_interpreterStubNum++;
#if INTERP_TRACING
if (s_interpreterStubNum < s_InterpreterStubMin.val(CLRConfig::INTERNAL_InterpreterStubMin)
|| s_interpreterStubNum > s_InterpreterStubMax.val(CLRConfig::INTERNAL_InterpreterStubMax))
{
TRACE_SKIPPED(clsName, methName, "stub num not in range, not interpreting.");
return CORJIT_SKIPPED;
}
if (s_DumpInterpreterStubsFlag.val(CLRConfig::INTERNAL_DumpInterpreterStubs))
{
unsigned hash = comp->getMethodHash(info->ftn);
fprintf(GetLogFile(), "Generating interpretation stub (# %d = 0x%x, hash = 0x%x) for %s:%s.\n",
s_interpreterStubNum, s_interpreterStubNum, hash, clsName, methName);
fflush(GetLogFile());
}
#endif
}
//
// Finally, generate an interpreter entry-point stub.
//
// @TODO: this structure clearly needs some sort of lifetime management. It is the moral equivalent
// of compiled code, and should be associated with an app domain. In addition, when I get to it, we should
// delete it when/if we actually compile the method. (Actually, that's complicated, since there may be
// VSD stubs still bound to the interpreter stub. The check there will get to the jitted code, but we want
// to eventually clean those up at some safe point...)
InterpreterMethodInfo* interpMethInfo = new InterpreterMethodInfo(comp, info);
if (ppInterpMethodInfo != nullptr)
{
*ppInterpMethodInfo = interpMethInfo;
}
interpMethInfo->m_stubNum = s_interpreterStubNum;
MethodDesc* methodDesc = reinterpret_cast<MethodDesc*>(info->ftn);
if (!jmpCall)
{
interpMethInfo = RecordInterpreterMethodInfoForMethodHandle(info->ftn, interpMethInfo);
}
#if FEATURE_INTERPRETER_DEADSIMPLE_OPT
unsigned offsetOfLd;
if (IsDeadSimpleGetter(comp, methodDesc, &offsetOfLd))
{
interpMethInfo->SetFlag<InterpreterMethodInfo::Flag_methIsDeadSimpleGetter>(true);
if (offsetOfLd == ILOffsetOfLdFldInDeadSimpleInstanceGetterDbg)
{
interpMethInfo->SetFlag<InterpreterMethodInfo::Flag_methIsDeadSimpleGetterIsDbgForm>(true);
}
else
{
_ASSERTE(offsetOfLd == ILOffsetOfLdFldInDeadSimpleInstanceGetterOpt);
}
}
#endif // FEATURE_INTERPRETER_DEADSIMPLE_OPT
// Used to initialize the arg offset information.
Stub* stub = NULL;
// We assume that the stack contains (with addresses growing upwards, assuming a downwards-growing stack):
//
// [Non-reg arg N-1]
// ...
// [Non-reg arg <# of reg args>]
// [return PC]
//
// Then push the register args to get:
//
// [Non-reg arg N-1]
// ...
// [Non-reg arg <# of reg args>]
// [return PC]
// [reg arg <# of reg args>-1]
// ...
// [reg arg 0]
//
// Pass the address of this argument array, and the MethodDesc pointer for the method, as arguments to
// Interpret.
//
// So the structure of the code will look like this (in the non-ILstub case):
//
#if defined(HOST_X86) || defined(HOST_AMD64)
// push ebp
// mov ebp, esp
// [if there are register arguments in ecx or edx, push them]
// ecx := addr of InterpretMethodInfo for the method to be interpreted.
// edx = esp /*pointer to argument structure*/
// call to Interpreter::InterpretMethod
// [if we pushed register arguments, increment esp by the right amount.]
// pop ebp
// ret <n> ; where <n> is the number of argument stack slots in the call to the stub.
#elif defined (HOST_ARM)
// TODO.
#endif
// TODO: much of the interpreter stub code should be is shareable. In the non-IL stub case,
// at least, we could have a small per-method stub that puts the address of the method-specific
// InterpreterMethodInfo into eax, and then branches to a shared part. Probably we would want to
// always push all integer args on x86, as we do already on ARM. On ARM, we'd need several versions
// of the shared stub, for different numbers of floating point register args, cross different kinds of
// HFA return values. But these could still be shared, and the per-method stub would decide which of
// these to target.
//
// In the IL stub case, which uses eax, it would be problematic to do this sharing.
StubLinkerCPU sl;
MethodDesc* pMD = reinterpret_cast<MethodDesc*>(info->ftn);
if (!jmpCall)
{
sl.Init();
#if defined(HOST_X86) || defined(HOST_AMD64)
#if defined(HOST_X86)
sl.X86EmitPushReg(kEBP);
sl.X86EmitMovRegReg(kEBP, static_cast<X86Reg>(kESP_Unsafe));
#endif
#elif defined(HOST_ARM)
// On ARM we use R12 as a "scratch" register -- callee-trashed, not used
// for arguments.
ThumbReg r11 = ThumbReg(11);
ThumbReg r12 = ThumbReg(12);
#elif defined(HOST_ARM64)
// x8 through x15 are scratch registers on ARM64.
IntReg x8 = IntReg(8);
IntReg x9 = IntReg(9);
#elif defined(HOST_RISCV64)
#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())
{
_ASSERTE(info->args.callConv & CORINFO_CALLCONV_HASTHIS);
hasThis = true;
totalArgs++; sigArgsPlusThis++;
}
if (methodDesc->HasRetBuffArg())
{
hasRetBuff = true;
retBuffArgIndex = totalArgs;
totalArgs++;
}
if (sig.GetCallingConventionInfo() & CORINFO_CALLCONV_VARARG)
{
isVarArg = true;
vaSigCookieIndex = totalArgs;
totalArgs++;
}
if (sig.GetCallingConventionInfo() & CORINFO_CALLCONV_PARAMTYPE)
{
_ASSERTE(info->args.callConv & CORINFO_CALLCONV_PARAMTYPE);
hasGenericsContextArg = true;
genericsContextArgIndex = totalArgs;
totalArgs++;
}
// The non-this sig args have indices starting after these.
// We will first encode the arg offsets as *negative* offsets from the address above the first
// stack arg, and later add in the total size of the stack args to get a positive offset.
// The first sigArgsPlusThis elements are the offsets of the IL-addressable arguments. After that,
// there may be up to two more: generics context arg, if present, and return buff pointer, if present.
// (Note that the latter is actually passed after the "this" pointer, or else first if no "this" pointer
// is present. We re-arrange to preserve the easy IL-addressability.)
ArgState argState(totalArgs);
// This is the permutation that translates from an index in the argOffsets/argIsReg arrays to
// the platform-specific order in which the arguments are passed.
unsigned* argPerm = new unsigned[totalArgs];
// The number of register argument slots we end up pushing.
unsigned short regArgsFound = 0;
unsigned physArgIndex = 0;
#if defined(HOST_ARM)
// The stub linker has a weird little limitation: all stubs it's used
// for on ARM push some callee-saved register, so the unwind info
// code was written assuming at least one would be pushed. I don't know how to
// fix it, so I'm meeting this requirement, by pushing one callee-save.
#define STUB_LINK_EMIT_PROLOG_REQUIRES_CALLEE_SAVE_PUSH 1
#if STUB_LINK_EMIT_PROLOG_REQUIRES_CALLEE_SAVE_PUSH
const int NumberOfCalleeSaveRegsToPush = 1;
#else
const int NumberOfCalleeSaveRegsToPush = 0;
#endif
// The "1" here is for the return address.
const int NumberOfFixedPushes = 1 + NumberOfCalleeSaveRegsToPush;
#elif defined(HOST_ARM64)
// FP, LR
const int NumberOfFixedPushes = 2;
#endif
#if defined(FEATURE_HFA)
#if defined(HOST_ARM) || defined(HOST_ARM64)
// On ARM, a non-retBuffArg method that returns a struct type might be an HFA return. Figure
// that out.
unsigned HFARetTypeSize = 0;
#endif
#if defined(HOST_ARM64)
unsigned cHFAVars = 0;
#endif
if (info->args.retType == CORINFO_TYPE_VALUECLASS
&& (comp->getHFAType(info->args.retTypeClass) != CORINFO_HFA_ELEM_NONE)
&& info->args.getCallConv() != CORINFO_CALLCONV_VARARG)
{
HFARetTypeSize = getClassSize(info->args.retTypeClass);
#if defined(HOST_ARM)
// Round up to a double boundary;
HFARetTypeSize = ((HFARetTypeSize+ sizeof(double) - 1) / sizeof(double)) * sizeof(double);
#elif defined(HOST_ARM64)
// We don't need to round it up to double. Unlike ARM, whether it's a float or a double each field will
// occupy one slot. We'll handle the stack alignment in the prolog where we have all the information about
// what is going to be pushed on the stack.
// Instead on ARM64 we'll need to know how many slots we'll need.
// for instance a VT with two float fields will have the same size as a VT with 1 double field. (ARM64TODO: Verify it)
// It works on ARM because the overlapping layout of the floating point registers
// but it won't work on ARM64.
cHFAVars = (comp->getHFAType(info->args.retTypeClass) == CORINFO_HFA_ELEM_FLOAT) ? HFARetTypeSize/sizeof(float) : HFARetTypeSize/sizeof(double);
#endif
}
#endif // defined(FEATURE_HFA)
_ASSERTE_MSG((info->args.callConv & (CORINFO_CALLCONV_EXPLICITTHIS)) == 0,
"Don't yet handle EXPLICITTHIS calling convention modifier.");
switch (info->args.callConv & CORINFO_CALLCONV_MASK)
{
case CORINFO_CALLCONV_DEFAULT:
case CORINFO_CALLCONV_VARARG:
{
unsigned firstSigArgIndex = 0;
if (hasThis)
{
argPerm[0] = physArgIndex; physArgIndex++;
argState.AddArg(0);
firstSigArgIndex++;
}
if (hasRetBuff)
{
argPerm[retBuffArgIndex] = physArgIndex; physArgIndex++;
argState.AddArg(retBuffArgIndex);
}
if (isVarArg)
{
argPerm[vaSigCookieIndex] = physArgIndex; physArgIndex++;
interpMethInfo->m_varArgHandleArgNum = vaSigCookieIndex;
argState.AddArg(vaSigCookieIndex);
}
#if defined(HOST_ARM) || defined(HOST_AMD64) || defined(HOST_ARM64) || defined(HOST_RISCV64)
// Generics context comes before args on ARM. Would be better if I factored this out as a call,
// to avoid large swatches of duplicate code.
if (hasGenericsContextArg)
{
argPerm[genericsContextArgIndex] = physArgIndex; physArgIndex++;
argState.AddArg(genericsContextArgIndex);
}
#endif // HOST_ARM || HOST_AMD64 || HOST_ARM64 || HOST_RISCV64
CORINFO_ARG_LIST_HANDLE argPtr = info->args.args;
// Some arguments are have been passed in registers, some in memory. We must generate code that
// moves the register arguments to memory, and determines a pointer into the stack from which all
// the arguments can be accessed, according to the offsets in "argOffsets."
//
// In the first pass over the arguments, we will label and count the register arguments, and
// initialize entries in "argOffsets" for the non-register arguments -- relative to the SP at the
// time of the call. Then when we have counted the number of register arguments, we will adjust
// the offsets for the non-register arguments to account for those. Then, in the second pass, we
// will push the register arguments on the stack, and capture the final stack pointer value as
// the argument vector pointer.
CORINFO_CLASS_HANDLE vcTypeRet;
// This iteration starts at the first signature argument, and iterates over all the
// canonical indices for the signature arguments.
for (unsigned k = firstSigArgIndex; k < sigArgsPlusThis; k++)
{
argPerm[k] = physArgIndex; physArgIndex++;
CorInfoTypeWithMod argTypWithMod = comp->getArgType(&info->args, argPtr, &vcTypeRet);
CorInfoType argType = strip(argTypWithMod);
switch (argType)
{
case CORINFO_TYPE_UNDEF:
case CORINFO_TYPE_VOID:
case CORINFO_TYPE_VAR:
_ASSERTE_ALL_BUILDS(false); // Should not happen;
break;
// One integer slot arguments:
case CORINFO_TYPE_BOOL:
case CORINFO_TYPE_CHAR:
case CORINFO_TYPE_BYTE:
case CORINFO_TYPE_UBYTE:
case CORINFO_TYPE_SHORT:
case CORINFO_TYPE_USHORT:
case CORINFO_TYPE_INT:
case CORINFO_TYPE_UINT:
case CORINFO_TYPE_NATIVEINT:
case CORINFO_TYPE_NATIVEUINT:
case CORINFO_TYPE_BYREF:
case CORINFO_TYPE_CLASS:
case CORINFO_TYPE_STRING:
case CORINFO_TYPE_PTR:
argState.AddArg(k);
break;
// Two integer slot arguments.
case CORINFO_TYPE_LONG:
case CORINFO_TYPE_ULONG:
#if defined(HOST_X86)
// Longs are always passed on the stack -- with no obvious alignment.
argState.AddArg(k, 2, /*noReg*/true);
#elif defined(HOST_ARM)
// LONGS have 2-reg alignment; inc reg if necessary.
argState.AddArg(k, 2, /*noReg*/false, /*twoSlotAlign*/true);
#elif defined(HOST_AMD64) || defined(HOST_ARM64) || defined(HOST_LOONGARCH64) || defined(HOST_RISCV64)
argState.AddArg(k);
#else
#error unknown platform
#endif
break;
// One float slot args:
case CORINFO_TYPE_FLOAT:
#if defined(HOST_X86)
argState.AddArg(k, 1, /*noReg*/true);
#elif defined(HOST_ARM)
argState.AddFPArg(k, 1, /*twoSlotAlign*/false);
#elif defined(HOST_AMD64) || defined(HOST_ARM64) || defined(HOST_LOONGARCH64) || defined(HOST_RISCV64)
argState.AddFPArg(k, 1, false);
#else
#error unknown platform
#endif
break;
// Two float slot args
case CORINFO_TYPE_DOUBLE:
#if defined(HOST_X86)
argState.AddArg(k, 2, /*noReg*/true);
#elif defined(HOST_ARM)
argState.AddFPArg(k, 2, /*twoSlotAlign*/true);
#elif defined(HOST_AMD64) || defined(HOST_ARM64) || defined(HOST_LOONGARCH64) || defined(HOST_RISCV64)
argState.AddFPArg(k, 1, false);
#else
#error unknown platform
#endif
break;
// Value class args:
case CORINFO_TYPE_VALUECLASS:
case CORINFO_TYPE_REFANY:
{
unsigned sz = getClassSize(vcTypeRet);
unsigned szSlots = max(1, sz / sizeof(void*));
#if defined(HOST_X86)
argState.AddArg(k, static_cast<short>(szSlots), /*noReg*/true);
#elif defined(HOST_AMD64)
argState.AddArg(k, static_cast<short>(szSlots));
#elif defined(HOST_ARM) || defined(HOST_ARM64)
// TODO: handle Vector64, Vector128 types
CorInfoHFAElemType hfaType = comp->getHFAType(vcTypeRet);
if (CorInfoTypeIsFloatingPoint(hfaType))
{
argState.AddFPArg(k, szSlots,
#if defined(HOST_ARM)
/*twoSlotAlign*/ (hfaType == CORINFO_HFA_ELEM_DOUBLE)
#elif defined(HOST_ARM64)
/*twoSlotAlign*/ false // unlike ARM32 FP args always consume 1 slot on ARM64
#endif
);
}
else
{
unsigned align = comp->getClassAlignmentRequirement(vcTypeRet, FALSE);
argState.AddArg(k, static_cast<short>(szSlots), /*noReg*/false,
#if defined(HOST_ARM)
/*twoSlotAlign*/ (align == 8)
#elif defined(HOST_ARM64)
/*twoSlotAlign*/ false
#endif
);
}
#elif defined(HOST_LOONGARCH64)
argState.AddArg(k, static_cast<short>(szSlots));
#elif defined(HOST_RISCV64)
argState.AddArg(k, static_cast<short>(szSlots));
#else
#error unknown platform
#endif
}
break;
default:
_ASSERTE_MSG(false, "should not reach here, unknown arg type");
}
argPtr = comp->getArgNext(argPtr);
}
#if defined(HOST_X86)
// Generics context comes last on HOST_X86. Would be better if I factored this out as a call,
// to avoid large swatches of duplicate code.
if (hasGenericsContextArg)
{
argPerm[genericsContextArgIndex] = physArgIndex; physArgIndex++;
argState.AddArg(genericsContextArgIndex);
}
// Now we have counted the number of register arguments, so we can update the offsets for the
// non-register arguments. "+ 2" below is to account for the return address from the call, and
// pushing of EBP.
unsigned short stackArgBaseOffset = (argState.numRegArgs + 2 + argState.callerArgStackSlots) * sizeof(void*);
unsigned intRegArgBaseOffset = 0;
#elif defined(HOST_ARM)
// We're choosing to always push all arg regs on ARM -- this is the only option
// that ThumbEmitProlog currently gives.
argState.numRegArgs = 4;
// On ARM, we push the (integer) arg regs before we push the return address, so we don't add an
// extra constant. And the offset is the address of the last pushed argument, which is the first
// stack argument in signature order.
// Round up to a double boundary...
unsigned fpStackSlots = ((argState.numFPRegArgSlots + 1) / 2) * 2;
unsigned intRegArgBaseOffset = (fpStackSlots + NumberOfFixedPushes) * sizeof(void*);
unsigned short stackArgBaseOffset = intRegArgBaseOffset + (argState.numRegArgs) * sizeof(void*);
#elif defined(HOST_ARM64)
// See StubLinkerCPU::EmitProlog for the layout of the stack
unsigned intRegArgBaseOffset = (argState.numFPRegArgSlots) * sizeof(void*);
unsigned short stackArgBaseOffset = (unsigned short) ((argState.numRegArgs + argState.numFPRegArgSlots) * sizeof(void*));
#elif defined(UNIX_AMD64_ABI)
unsigned intRegArgBaseOffset = 0;
unsigned short stackArgBaseOffset = (2 + argState.numRegArgs + argState.numFPRegArgSlots) * sizeof(void*);
#elif defined(HOST_AMD64)
unsigned short stackArgBaseOffset = (argState.numRegArgs) * sizeof(void*);
#elif defined(HOST_LOONGARCH64)
// See StubLinkerCPU::EmitProlog for the layout of the stack
unsigned intRegArgBaseOffset = (argState.numFPRegArgSlots) * sizeof(void*);
unsigned short stackArgBaseOffset = (unsigned short) ((argState.numRegArgs + argState.numFPRegArgSlots) * sizeof(void*));
#elif defined(HOST_RISCV64)
unsigned intRegArgBaseOffset = (argState.numFPRegArgSlots) * sizeof(void*);
unsigned short stackArgBaseOffset = (unsigned short) ((argState.numRegArgs + argState.numFPRegArgSlots) * sizeof(void*));
#else
#error unsupported platform
#endif
#if defined(HOST_ARM)
WORD regArgMask = 0;
#endif // defined(HOST_ARM)
// argPerm maps from an index into the argOffsets/argIsReg arrays to
// the order that the arguments are passed.
unsigned* argPermInverse = new unsigned[totalArgs];
for (unsigned t = 0; t < totalArgs; t++)
{
argPermInverse[argPerm[t]] = t;
}
for (unsigned kk = 0; kk < totalArgs; kk++)
{
// Let "k" be the index of the kk'th input in the argOffsets and argIsReg arrays.
// To compute "k" we need to invert argPerm permutation -- determine the "k" such
// that argPerm[k] == kk.
unsigned k = argPermInverse[kk];
_ASSERTE(k < totalArgs);
if (argState.argIsReg[k] == ArgState::ARS_IntReg)
{
regArgsFound++;
// If any int reg args are used on ARM, we push them all (in ThumbEmitProlog)
#if defined(HOST_X86)
if (regArgsFound == 1)
{
if (!jmpCall) { sl.X86EmitPushReg(kECX); }
argState.argOffsets[k] = (argState.numRegArgs - regArgsFound)*sizeof(void*); // General form, good for general # of reg args.
}
else
{
_ASSERTE(regArgsFound == 2);
if (!jmpCall) { sl.X86EmitPushReg(kEDX); }
argState.argOffsets[k] = (argState.numRegArgs - regArgsFound)*sizeof(void*);
}
#elif defined(HOST_ARM) || defined(HOST_ARM64) || defined(UNIX_AMD64_ABI)
argState.argOffsets[k] += intRegArgBaseOffset;
#elif defined(HOST_AMD64)
// First home the register arguments in the stack space allocated by the caller.
// Refer to Stack Allocation on x64 [http://msdn.microsoft.com/en-US/library/ew5tede7(v=vs.80).aspx]
X86Reg argRegs[] = { kECX, kEDX, kR8, kR9 };
if (!jmpCall) { sl.X86EmitIndexRegStoreRSP(regArgsFound * sizeof(void*), argRegs[regArgsFound - 1]); }
argState.argOffsets[k] = (regArgsFound - 1) * sizeof(void*);
#elif defined(HOST_LOONGARCH64)
argState.argOffsets[k] += intRegArgBaseOffset;
#elif defined(HOST_RISCV64)
argState.argOffsets[k] += intRegArgBaseOffset;
#else
#error unsupported platform
#endif
}
#if defined(HOST_AMD64) && !defined(UNIX_AMD64_ABI)
else if (argState.argIsReg[k] == ArgState::ARS_FloatReg)
{
// Increment regArgsFound since float/int arguments have overlapping registers.
regArgsFound++;
// Home the float arguments.
X86Reg argRegs[] = { kXMM0, kXMM1, kXMM2, kXMM3 };
if (!jmpCall) { sl.X64EmitMovSDToMem(argRegs[regArgsFound - 1], static_cast<X86Reg>(kESP_Unsafe), regArgsFound * sizeof(void*)); }
argState.argOffsets[k] = (regArgsFound - 1) * sizeof(void*);
}
#endif
else if (argState.argIsReg[k] == ArgState::ARS_NotReg)
{
argState.argOffsets[k] += stackArgBaseOffset;
}
// So far, x86 doesn't have any FP reg args, and ARM and ARM64 puts them at offset 0, so no
// adjustment is necessary (yet) for arguments passed in those registers.
}
delete[] argPermInverse;
}
break;
case IMAGE_CEE_CS_CALLCONV_C:
NYI_INTERP("GenerateInterpreterStub -- IMAGE_CEE_CS_CALLCONV_C");
break;
case IMAGE_CEE_CS_CALLCONV_STDCALL:
NYI_INTERP("GenerateInterpreterStub -- IMAGE_CEE_CS_CALLCONV_STDCALL");
break;
case IMAGE_CEE_CS_CALLCONV_THISCALL:
NYI_INTERP("GenerateInterpreterStub -- IMAGE_CEE_CS_CALLCONV_THISCALL");
break;
case IMAGE_CEE_CS_CALLCONV_FASTCALL:
NYI_INTERP("GenerateInterpreterStub -- IMAGE_CEE_CS_CALLCONV_FASTCALL");
break;
case CORINFO_CALLCONV_FIELD:
NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_FIELD");
break;
case CORINFO_CALLCONV_LOCAL_SIG:
NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_LOCAL_SIG");
break;
case CORINFO_CALLCONV_PROPERTY:
NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_PROPERTY");
break;
case CORINFO_CALLCONV_UNMANAGED:
NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_UNMANAGED");
break;
case CORINFO_CALLCONV_NATIVEVARARG:
NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_NATIVEVARARG");
break;
default:
_ASSERTE_ALL_BUILDS(false); // shouldn't get here
}
delete[] argPerm;
PCODE interpretMethodFunc;
if (!jmpCall)
{
switch (info->args.retType)
{
case CORINFO_TYPE_FLOAT:
interpretMethodFunc = reinterpret_cast<PCODE>(&InterpretMethodFloat);
break;
case CORINFO_TYPE_DOUBLE:
interpretMethodFunc = reinterpret_cast<PCODE>(&InterpretMethodDouble);
break;
default:
interpretMethodFunc = reinterpret_cast<PCODE>(&InterpretMethod);
break;
}
// The argument registers have been pushed by now, so we can use them.
#if defined(HOST_X86)
// First arg is pointer to the base of the ILargs arr -- i.e., the current stack value.
sl.X86EmitMovRegReg(kEDX, static_cast<X86Reg>(kESP_Unsafe));
// InterpretMethod uses F_CALL_CONV == __fastcall; pass 2 args in regs.
#if INTERP_ILSTUBS
if (pMD->IsILStub())
{
// Third argument is stubcontext, in eax.
sl.X86EmitPushReg(kEAX);
}
else
#endif
{
// For a non-ILStub method, push NULL as the StubContext argument.
sl.X86EmitZeroOutReg(kECX);
sl.X86EmitPushReg(kECX);
}
// sl.X86EmitAddReg(kECX, reinterpret_cast<UINT>(interpMethInfo));
sl.X86EmitRegLoad(kECX, reinterpret_cast<UINT>(interpMethInfo));
sl.X86EmitCall(sl.NewExternalCodeLabel(interpretMethodFunc), 0);
// Now we will deallocate the stack slots we pushed to hold register arguments.
if (argState.numRegArgs > 0)
{
sl.X86EmitAddEsp(argState.numRegArgs * sizeof(void*));
}
sl.X86EmitPopReg(kEBP);
sl.X86EmitReturn(static_cast<WORD>(argState.callerArgStackSlots * sizeof(void*)));
#elif defined(UNIX_AMD64_ABI)
bool hasTwoRetSlots = info->args.retType == CORINFO_TYPE_VALUECLASS &&
getClassSize(info->args.retTypeClass) == 16;
int fixedTwoSlotSize = 16;
int argSize = (argState.numFPRegArgSlots + argState.numRegArgs) * sizeof(void*);
int stackSize = argSize + fixedTwoSlotSize; // Fixed two slot for possible "retbuf", access address by "m_ilArgs-16"
if (stackSize % 16 == 0) { // for $rsp align requirement
stackSize += 8;
}
sl.X86EmitSubEsp(stackSize);
X86Reg intArgsRegs[] = {ARGUMENT_kREG1, ARGUMENT_kREG2, kRDX, kRCX, kR8, kR9};
int indexGP = 0;
int indexFP = 0;
for (int i = 0; i < argState.numRegArgs + argState.numFPRegArgSlots; i++)
{
int offs = i * sizeof(void*) + 16;
if (argState.fpArgsUsed & (1 << i))
{
sl.X64EmitMovSDToMem(static_cast<X86Reg>(indexFP), static_cast<X86Reg>(kESP_Unsafe), offs);
indexFP++;
}
else
{
sl.X86EmitIndexRegStoreRSP(offs, intArgsRegs[indexGP]);
indexGP++;
}
}
// Pass "ilArgs", i.e. just the point where registers have been homed, as 2nd arg.
sl.X86EmitIndexLeaRSP(ARGUMENT_kREG2, static_cast<X86Reg>(kESP_Unsafe), fixedTwoSlotSize);
// If we have IL stubs pass the stub context in R10 or else pass NULL.
#if INTERP_ILSTUBS
if (pMD->IsILStub())
{
sl.X86EmitMovRegReg(kRDX, kR10);
}
else
#endif
{
// For a non-ILStub method, push NULL as the StubContext argument.
sl.X86EmitZeroOutReg(ARGUMENT_kREG1);
sl.X86EmitMovRegReg(kRDX, ARGUMENT_kREG1);
}
sl.X86EmitRegLoad(ARGUMENT_kREG1, reinterpret_cast<UINT_PTR>(interpMethInfo));
sl.X86EmitCall(sl.NewExternalCodeLabel(interpretMethodFunc), 0);
if (hasTwoRetSlots) {
sl.X86EmitEspOffset(0x8b, kRAX, 0);
sl.X86EmitEspOffset(0x8b, kRDX, 8);
}
sl.X86EmitAddEsp(stackSize);
sl.X86EmitReturn(0);
#elif defined(HOST_AMD64)
// Pass "ilArgs", i.e. just the point where registers have been homed, as 2nd arg
sl.X86EmitIndexLeaRSP(ARGUMENT_kREG2, static_cast<X86Reg>(kESP_Unsafe), 8);
// Allocate space for homing callee's (InterpretMethod's) arguments.
// Calling convention requires a default allocation space of 4,
// but to double align the stack frame, we'd allocate 5.
int interpMethodArgSize = 5 * sizeof(void*);
sl.X86EmitSubEsp(interpMethodArgSize);
// If we have IL stubs pass the stub context in R10 or else pass NULL.
#if INTERP_ILSTUBS
if (pMD->IsILStub())
{
sl.X86EmitMovRegReg(kR8, kR10);
}
else
#endif
{
// For a non-ILStub method, push NULL as the StubContext argument.
sl.X86EmitZeroOutReg(ARGUMENT_kREG1);
sl.X86EmitMovRegReg(kR8, ARGUMENT_kREG1);
}
sl.X86EmitRegLoad(ARGUMENT_kREG1, reinterpret_cast<UINT_PTR>(interpMethInfo));
sl.X86EmitCall(sl.NewExternalCodeLabel(interpretMethodFunc), 0);
sl.X86EmitAddEsp(interpMethodArgSize);
sl.X86EmitReturn(0);
#elif defined(HOST_ARM)
// We have to maintain 8-byte stack alignment. So if the number of
// slots we would normally push is not a multiple of two, add a random
// register. (We will not pop this register, but rather, increment
// sp by an amount that includes it.)
bool oddPushes = (((argState.numRegArgs + NumberOfFixedPushes) % 2) != 0);
UINT stackFrameSize = 0;
if (oddPushes) stackFrameSize = sizeof(void*);
// Now, if any FP regs are used as arguments, we will copy those to the stack; reserve space for that here.
// (We push doubles to keep the stack aligned...)
unsigned short doublesToPush = (argState.numFPRegArgSlots + 1)/2;
stackFrameSize += (doublesToPush*2*sizeof(void*));
// The last argument here causes this to generate code to push all int arg regs.
sl.ThumbEmitProlog(/*cCalleeSavedRegs*/NumberOfCalleeSaveRegsToPush, /*cbStackFrame*/stackFrameSize, /*fPushArgRegs*/TRUE);
// Now we will generate code to copy the floating point registers to the stack frame.
if (doublesToPush > 0)
{
sl.ThumbEmitStoreMultipleVFPDoubleReg(ThumbVFPDoubleReg(0), thumbRegSp, doublesToPush*2);
}
#if INTERP_ILSTUBS
if (pMD->IsILStub())
{
// Third argument is stubcontext, in r12.
sl.ThumbEmitMovRegReg(ThumbReg(2), ThumbReg(12));
}
else
#endif
{
// For a non-ILStub method, push NULL as the third StubContext argument.
sl.ThumbEmitMovConstant(ThumbReg(2), 0);
}
// Second arg is pointer to the base of the ILargs arr -- i.e., the current stack value.
sl.ThumbEmitMovRegReg(ThumbReg(1), thumbRegSp);
// First arg is the pointer to the interpMethInfo structure.
sl.ThumbEmitMovConstant(ThumbReg(0), reinterpret_cast<int>(interpMethInfo));
// If there's an HFA return, add space for that.
if (HFARetTypeSize > 0)
{
sl.ThumbEmitSubSp(HFARetTypeSize);
}
// Now we can call the right method.
// No "direct call" instruction, so load into register first. Can use R3.
sl.ThumbEmitMovConstant(ThumbReg(3), static_cast<int>(interpretMethodFunc));
sl.ThumbEmitCallRegister(ThumbReg(3));
// If there's an HFA return, copy to FP regs, and deallocate the stack space.
if (HFARetTypeSize > 0)
{
sl.ThumbEmitLoadMultipleVFPDoubleReg(ThumbVFPDoubleReg(0), thumbRegSp, HFARetTypeSize/sizeof(void*));
sl.ThumbEmitAddSp(HFARetTypeSize);
}
sl.ThumbEmitEpilog();
#elif defined(HOST_ARM64)
UINT stackFrameSize = argState.numFPRegArgSlots;
sl.EmitProlog(argState.numRegArgs, argState.numFPRegArgSlots, 0 /*cCalleeSavedRegs*/, static_cast<unsigned short>(cHFAVars*sizeof(void*)));
#if INTERP_ILSTUBS
if (pMD->IsILStub())
{
// Third argument is stubcontext, in x12 (METHODDESC_REGISTER)
sl.EmitMovReg(IntReg(2), IntReg(12));
}
else
#endif
{
// For a non-ILStub method, push NULL as the third stubContext argument
sl.EmitMovConstant(IntReg(2), 0);
}
// Second arg is pointer to the basei of the ILArgs -- i.e., the current stack value
sl.EmitAddImm(IntReg(1), RegSp, sl.GetSavedRegArgsOffset());
// First arg is the pointer to the interpMethodInfo structure
#if INTERP_ILSTUBS
if (!pMD->IsILStub())
#endif
{
// interpMethodInfo is already in x8, so copy it from x8
sl.EmitMovReg(IntReg(0), IntReg(8));
}
#if INTERP_ILSTUBS
else
{
// We didn't do the short-circuiting, therefore interpMethInfo is
// not stored in a register (x8) before. so do it now.
sl.EmitMovConstant(IntReg(0), reinterpret_cast<UINT64>(interpMethInfo));
}
#endif
sl.EmitCallLabel(sl.NewExternalCodeLabel((LPVOID)interpretMethodFunc), FALSE, FALSE);
// If there's an HFA return, copy to FP regs
if (cHFAVars > 0)
{
for (unsigned i=0; i<=(cHFAVars/2)*2;i+=2)
sl.EmitLoadStoreRegPairImm(StubLinkerCPU::eLOAD, VecReg(i), VecReg(i+1), RegSp, i*sizeof(void*));
if ((cHFAVars % 2) == 1)
sl.EmitLoadStoreRegImm(StubLinkerCPU::eLOAD,VecReg(cHFAVars-1), RegSp, cHFAVars*sizeof(void*));
}
sl.EmitEpilog();
#elif defined(HOST_LOONGARCH64)
assert(!"unimplemented on LOONGARCH yet");
#elif defined(HOST_RISCV64)
bool hasTwoRetSlots = info->args.retType == CORINFO_TYPE_VALUECLASS &&
getClassSize(info->args.retTypeClass) == 16;
UINT stackFrameSize = argState.numFPRegArgSlots;
sl.EmitProlog(argState.numRegArgs, argState.numFPRegArgSlots, hasTwoRetSlots ? 2 * sizeof(void*) : 0);
#if INTERP_ILSTUBS
if (pMD->IsILStub())
{
// Third argument is stubcontext, in t2 (METHODDESC_REGISTER).
sl.EmitMovReg(IntReg(12), IntReg(7));
}
else
#endif
{
// For a non-ILStub method, push NULL as the third StubContext argument.
sl.EmitMovConstant(IntReg(12), 0);
}
// Second arg is pointer to the base of the ILargs arr -- i.e., the current stack value.
sl.EmitAddImm(IntReg(11), RegSp, sl.GetSavedRegArgsOffset());
// First arg is the pointer to the interpMethodInfo structure
sl.EmitMovConstant(IntReg(10), reinterpret_cast<UINT64>(interpMethInfo));
sl.EmitCallLabel(sl.NewExternalCodeLabel((LPVOID)interpretMethodFunc), FALSE, FALSE);
if (hasTwoRetSlots)
{
// TODO: handle return registers to use int or float registers
sl.EmitLoad(IntReg(10), RegSp, 0);
sl.EmitLoad(IntReg(11), RegSp, 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)
{
_ASSERTE(doJmpCall);
jitInfo = new CEEInfo(pMD, true);
CORINFO_METHOD_INFO methInfo;
GCX_PREEMP();
jitInfo->getMethodInfo(CORINFO_METHOD_HANDLE(pMD), &methInfo, NULL);
GenerateInterpreterStub(jitInfo, &methInfo, NULL, 0, &interpMethInfo, true);
}
}
UNREACHABLE();
}
void Interpreter::JitMethodIfAppropriate(InterpreterMethodInfo* interpMethInfo, bool force)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
unsigned int MaxInterpretCount = s_InterpreterJITThreshold.val(CLRConfig::INTERNAL_InterpreterJITThreshold);
bool scheduleTieringBackgroundWork = false;
TieredCompilationManager *tieredCompilationManager = GetAppDomain()->GetTieredCompilationManager();
if (force || interpMethInfo->m_invocations > MaxInterpretCount)
{
GCX_PREEMP();
MethodDesc *md = reinterpret_cast<MethodDesc *>(interpMethInfo->m_method);
PCODE stub = md->GetNativeCode();
if (InterpretationStubToMethodInfo(stub) == md)
{
#if INTERP_TRACING
if (s_TraceInterpreterJITTransitionFlag.val(CLRConfig::INTERNAL_TraceInterpreterJITTransition))
{
fprintf(GetLogFile(), "JITting method %s:%s.\n", md->m_pszDebugClassName, md->m_pszDebugMethodName);
}
#endif // INTERP_TRACING
CORJIT_FLAGS jitFlags(CORJIT_FLAGS::CORJIT_FLAG_MAKEFINALCODE);
NewHolder<COR_ILMETHOD_DECODER> pDecoder(NULL);
// Dynamic methods (e.g., IL stubs) do not have an IL decoder but may
// require additional flags. Ordinary methods require the opposite.
if (md->IsDynamicMethod())
{
jitFlags.Add(md->AsDynamicMethodDesc()->GetILStubResolver()->GetJitFlags());
}
else
{
COR_ILMETHOD_DECODER::DecoderStatus status;
pDecoder = new COR_ILMETHOD_DECODER(md->GetILHeader(),
md->GetMDImport(),
&status);
}
// This used to be a synchronous jit and could be made so again if desired,
// but using ASP .NET MusicStore as an example scenario the performance is
// better doing the JIT asynchronously. Given the not-on-by-default nature of the
// interpreter I didn't wring my hands too much trying to determine the ideal
// policy.
#ifdef FEATURE_TIERED_COMPILATION
CodeVersionManager::LockHolder _lockHolder;
NativeCodeVersion activeCodeVersion = md->GetCodeVersionManager()->GetActiveILCodeVersion(md).GetActiveNativeCodeVersion(md);
ILCodeVersion ilCodeVersion = activeCodeVersion.GetILCodeVersion();
if (!activeCodeVersion.IsFinalTier() &&
!ilCodeVersion.HasAnyOptimizedNativeCodeVersion(activeCodeVersion))
{
tieredCompilationManager->AsyncPromoteToTier1(activeCodeVersion, &scheduleTieringBackgroundWork);
}
#else
#error FEATURE_INTERPRETER depends on FEATURE_TIERED_COMPILATION now
#endif
}
}
if (scheduleTieringBackgroundWork)
{
tieredCompilationManager->TryScheduleBackgroundWorkerWithoutGCTrigger_Locked();
}
}
// static
HCIMPL3(float, InterpretMethodFloat, struct InterpreterMethodInfo* interpMethInfo, BYTE* ilArgs, void* stubContext)
{
FCALL_CONTRACT;
ARG_SLOT retVal = 0;
HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB(Frame::FRAME_ATTR_EXACT_DEPTH|Frame::FRAME_ATTR_CAPTURE_DEPTH_2);
retVal = (ARG_SLOT)Interpreter::InterpretMethodBody(interpMethInfo, false, ilArgs, stubContext);
HELPER_METHOD_FRAME_END();
return *reinterpret_cast<float*>(ArgSlotEndiannessFixup(&retVal, sizeof(float)));
}
HCIMPLEND
// static
HCIMPL3(double, InterpretMethodDouble, struct InterpreterMethodInfo* interpMethInfo, BYTE* ilArgs, void* stubContext)
{
FCALL_CONTRACT;
ARG_SLOT retVal = 0;
HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB(Frame::FRAME_ATTR_EXACT_DEPTH|Frame::FRAME_ATTR_CAPTURE_DEPTH_2);
retVal = Interpreter::InterpretMethodBody(interpMethInfo, false, ilArgs, stubContext);
HELPER_METHOD_FRAME_END();
return *reinterpret_cast<double*>(ArgSlotEndiannessFixup(&retVal, sizeof(double)));
}
HCIMPLEND
// static
HCIMPL3(INT64, InterpretMethod, struct InterpreterMethodInfo* interpMethInfo, BYTE* ilArgs, void* stubContext)
{
FCALL_CONTRACT;
ARG_SLOT retVal = 0;
HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB(Frame::FRAME_ATTR_EXACT_DEPTH|Frame::FRAME_ATTR_CAPTURE_DEPTH_2);
retVal = Interpreter::InterpretMethodBody(interpMethInfo, false, ilArgs, stubContext);
HELPER_METHOD_FRAME_END();
return static_cast<INT64>(retVal);
}
HCIMPLEND
bool Interpreter::IsInCalleesFrames(void* stackPtr)
{
// We assume a downwards_growing stack.
return stackPtr < (m_localVarMemory - sizeof(GSCookie));
}
// I want an enumeration with values for the second byte of 2-byte opcodes.
enum OPCODE_2BYTE {
#define OPDEF(c,s,pop,push,args,type,l,s1,s2,ctrl) TWOBYTE_##c = unsigned(s2),
#include "opcode.def"
#undef OPDEF
};
// Optimize the interpreter loop for speed.
#ifdef _MSC_VER
#pragma optimize("t", on)
#endif
// Duplicating code from JitHelpers for MonEnter,MonExit,MonEnter_Static,
// MonExit_Static because it sets up helper frame for the JIT.
static void MonitorEnter(Object* obj, BYTE* pbLockTaken)
{
OBJECTREF objRef = ObjectToOBJECTREF(obj);
if (objRef == NULL)
COMPlusThrow(kArgumentNullException);
GCPROTECT_BEGININTERIOR(pbLockTaken);
if (GET_THREAD()->CatchAtSafePointOpportunistic())
{
GET_THREAD()->PulseGCMode();
}
objRef->EnterObjMonitor();
if (pbLockTaken != 0) *pbLockTaken = 1;
GCPROTECT_END();
}
static void MonitorExit(Object* obj, BYTE* pbLockTaken)
{
OBJECTREF objRef = ObjectToOBJECTREF(obj);
if (objRef == NULL)
COMPlusThrow(kArgumentNullException);
if (!objRef->LeaveObjMonitor())
COMPlusThrow(kSynchronizationLockException);
if (pbLockTaken != 0) *pbLockTaken = 0;
if (GET_THREAD()->IsAbortRequested()) {
GET_THREAD()->HandleThreadAbort();
}
}
static void MonitorEnterStatic(AwareLock *lock, BYTE* pbLockTaken)
{
lock->Enter();
MONHELPER_STATE(*pbLockTaken = 1;)
}
static void MonitorExitStatic(AwareLock *lock, BYTE* pbLockTaken)
{
// Error, yield or contention
if (!lock->Leave())
COMPlusThrow(kSynchronizationLockException);
if (GET_THREAD()->IsAbortRequested()) {
GET_THREAD()->HandleThreadAbort();
}
}
AwareLock* Interpreter::GetMonitorForStaticMethod()
{
MethodDesc* pMD = reinterpret_cast<MethodDesc*>(m_methInfo->m_method);
CORINFO_LOOKUP_KIND kind;
{
GCX_PREEMP();
m_interpCeeInfo.getLocationOfThisType(m_methInfo->m_method, &kind);
}
if (!kind.needsRuntimeLookup)
{
OBJECTREF ref = pMD->GetMethodTable()->GetManagedClassObject();
return (AwareLock*) ref->GetSyncBlock()->GetMonitor();
}
else
{
CORINFO_CLASS_HANDLE classHnd = nullptr;
switch (kind.runtimeLookupKind)
{
case CORINFO_LOOKUP_CLASSPARAM:
{
CORINFO_CONTEXT_HANDLE ctxHnd = GetPreciseGenericsContext();
_ASSERTE_MSG((((size_t)ctxHnd & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS), "Precise context not class context");
classHnd = (CORINFO_CLASS_HANDLE) ((size_t)ctxHnd & ~CORINFO_CONTEXTFLAGS_CLASS);
}
break;
case CORINFO_LOOKUP_METHODPARAM:
{
CORINFO_CONTEXT_HANDLE ctxHnd = GetPreciseGenericsContext();
_ASSERTE_MSG((((size_t)ctxHnd & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD), "Precise context not method context");
MethodDesc* pMD = (MethodDesc*) (CORINFO_METHOD_HANDLE) ((size_t)ctxHnd & ~CORINFO_CONTEXTFLAGS_METHOD);
classHnd = (CORINFO_CLASS_HANDLE) pMD->GetMethodTable();
}
break;
default:
NYI_INTERP("Unknown lookup for synchronized methods");
break;
}
MethodTable* pMT = GetMethodTableFromClsHnd(classHnd);
OBJECTREF ref = pMT->GetManagedClassObject();
_ASSERTE(ref);
return (AwareLock*) ref->GetSyncBlock()->GetMonitor();
}
}
void Interpreter::DoMonitorEnterWork()
{
MethodDesc* pMD = reinterpret_cast<MethodDesc*>(m_methInfo->m_method);
if (pMD->IsSynchronized())
{
if (pMD->IsStatic())
{
AwareLock* lock = GetMonitorForStaticMethod();
MonitorEnterStatic(lock, &m_monAcquired);
}
else
{
MonitorEnter((Object*) m_thisArg, &m_monAcquired);
}
}
}
void Interpreter::DoMonitorExitWork()
{
MethodDesc* pMD = reinterpret_cast<MethodDesc*>(m_methInfo->m_method);
if (pMD->IsSynchronized())
{
if (pMD->IsStatic())
{
AwareLock* lock = GetMonitorForStaticMethod();
MonitorExitStatic(lock, &m_monAcquired);
}
else
{
MonitorExit((Object*) m_thisArg, &m_monAcquired);
}
}
}
void Interpreter::ExecuteMethod(ARG_SLOT* retVal, _Out_ bool* pDoJmpCall, _Out_ unsigned* pJmpCallToken)
{
#if INTERP_DYNAMIC_CONTRACTS
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#else
// Dynamic contract occupies too much stack.
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_COOPERATIVE;
#endif
*pDoJmpCall = false;
// Normally I'd prefer to declare these in small case-block scopes, but most C++ compilers
// do not realize that their lifetimes do not overlap, so that makes for a large stack frame.
// So I avoid that by outside declarations (sigh).
char offsetc, valc;
unsigned char argNumc;
unsigned short argNums;
INT32 vali;
INT64 vall;
InterpreterType it;
size_t sz;
unsigned short ops;
// Make sure that the .cctor for the current method's class has been run.
MethodDesc* pMD = reinterpret_cast<MethodDesc*>(m_methInfo->m_method);
EnsureClassInit(pMD->GetMethodTable());
#if INTERP_TRACING
const char* methName = eeGetMethodFullName(m_methInfo->m_method);
unsigned ilOffset = 0;
unsigned curInvocation = InterlockedIncrement(&s_totalInvocations);
if (s_TraceInterpreterEntriesFlag.val(CLRConfig::INTERNAL_TraceInterpreterEntries))
{
fprintf(GetLogFile(), "Entering method #%d (= 0x%x): %s.\n", curInvocation, curInvocation, methName);
fprintf(GetLogFile(), " arguments:\n");
PrintArgs();
}
#endif // INTERP_TRACING
#if LOOPS_VIA_INSTRS
unsigned instrs = 0;
#else
#if INTERP_PROFILE
unsigned instrs = 0;
#endif
#endif
EvalLoop:
GCX_ASSERT_COOP();
// Catch any exceptions raised.
EX_TRY {
// Optional features...
#define INTERPRETER_CHECK_LARGE_STRUCT_STACK_HEIGHT 1
#if INTERP_ILCYCLE_PROFILE
m_instr = CEE_COUNT; // Flag to indicate first instruction.
m_exemptCycles = 0;
#endif // INTERP_ILCYCLE_PROFILE
DoMonitorEnterWork();
INTERPLOG("START %d, %s\n", m_methInfo->m_stubNum, methName);
for (;;)
{
// TODO: verify that m_ILCodePtr is legal, and we haven't walked off the end of the IL array? (i.e., bad IL).
// Note that ExecuteBranch() should be called for every branch. That checks that we aren't either before or
// after the IL range. Here, we would only need to check that we haven't gone past the end (not before the beginning)
// because everything that doesn't call ExecuteBranch() should only add to m_ILCodePtr.
#if INTERP_TRACING
ilOffset = CurOffset();
#endif // _DEBUG
#if INTERP_TRACING
if (s_TraceInterpreterOstackFlag.val(CLRConfig::INTERNAL_TraceInterpreterOstack))
{
PrintOStack();
}
#if INTERPRETER_CHECK_LARGE_STRUCT_STACK_HEIGHT
_ASSERTE_MSG(LargeStructStackHeightIsValid(), "Large structure stack height invariant violated."); // Check the large struct stack invariant.
#endif
if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
{
fprintf(GetLogFile(), " %#4x: %s\n", ilOffset, ILOp(m_ILCodePtr));
fflush(GetLogFile());
}
#endif // INTERP_TRACING
#if LOOPS_VIA_INSTRS
instrs++;
#else
#if INTERP_PROFILE
instrs++;
#endif
#endif
#if INTERP_ILINSTR_PROFILE
#if INTERP_ILCYCLE_PROFILE
UpdateCycleCount();
#endif // INTERP_ILCYCLE_PROFILE
InterlockedIncrement(&s_ILInstrExecs[*m_ILCodePtr]);
#endif // INTERP_ILINSTR_PROFILE
switch (*m_ILCodePtr)
{
case CEE_NOP:
m_ILCodePtr++;
continue;
case CEE_BREAK: // TODO: interact with the debugger?
m_ILCodePtr++;
continue;
case CEE_LDARG_0:
LdArg(0);
break;
case CEE_LDARG_1:
LdArg(1);
break;
case CEE_LDARG_2:
LdArg(2);
break;
case CEE_LDARG_3:
LdArg(3);
break;
case CEE_LDLOC_0:
LdLoc(0);
m_ILCodePtr++;
continue;
case CEE_LDLOC_1:
LdLoc(1);
break;
case CEE_LDLOC_2:
LdLoc(2);
break;
case CEE_LDLOC_3:
LdLoc(3);
break;
case CEE_STLOC_0:
StLoc(0);
break;
case CEE_STLOC_1:
StLoc(1);
break;
case CEE_STLOC_2:
StLoc(2);
break;
case CEE_STLOC_3:
StLoc(3);
break;
case CEE_LDARG_S:
m_ILCodePtr++;
argNumc = *m_ILCodePtr;
LdArg(argNumc);
break;
case CEE_LDARGA_S:
m_ILCodePtr++;
argNumc = *m_ILCodePtr;
LdArgA(argNumc);
break;
case CEE_STARG_S:
m_ILCodePtr++;
argNumc = *m_ILCodePtr;
StArg(argNumc);
break;
case CEE_LDLOC_S:
argNumc = *(m_ILCodePtr + 1);
LdLoc(argNumc);
m_ILCodePtr += 2;
continue;
case CEE_LDLOCA_S:
m_ILCodePtr++;
argNumc = *m_ILCodePtr;
LdLocA(argNumc);
break;
case CEE_STLOC_S:
argNumc = *(m_ILCodePtr + 1);
StLoc(argNumc);
m_ILCodePtr += 2;
continue;
case CEE_LDNULL:
LdNull();
break;
case CEE_LDC_I4_M1:
LdIcon(-1);
break;
case CEE_LDC_I4_0:
LdIcon(0);
break;
case CEE_LDC_I4_1:
LdIcon(1);
m_ILCodePtr++;
continue;
case CEE_LDC_I4_2:
LdIcon(2);
break;
case CEE_LDC_I4_3:
LdIcon(3);
break;
case CEE_LDC_I4_4:
LdIcon(4);
break;
case CEE_LDC_I4_5:
LdIcon(5);
break;
case CEE_LDC_I4_6:
LdIcon(6);
break;
case CEE_LDC_I4_7:
LdIcon(7);
break;
case CEE_LDC_I4_8:
LdIcon(8);
break;
case CEE_LDC_I4_S:
valc = getI1(m_ILCodePtr + 1);
LdIcon(valc);
m_ILCodePtr += 2;
continue;
case CEE_LDC_I4:
vali = getI4LittleEndian(m_ILCodePtr + 1);
LdIcon(vali);
m_ILCodePtr += 5;
continue;
case CEE_LDC_I8:
vall = getI8LittleEndian(m_ILCodePtr + 1);
LdLcon(vall);
m_ILCodePtr += 9;
continue;
case CEE_LDC_R4:
// We use I4 here because we just care about the bit pattern.
// LdR4Con will push the right InterpreterType.
vali = getI4LittleEndian(m_ILCodePtr + 1);
LdR4con(vali);
m_ILCodePtr += 5;
continue;
case CEE_LDC_R8:
// We use I4 here because we just care about the bit pattern.
// LdR8Con will push the right InterpreterType.
vall = getI8LittleEndian(m_ILCodePtr + 1);
LdR8con(vall);
m_ILCodePtr += 9;
continue;
case CEE_DUP:
_ASSERTE(m_curStackHt > 0);
it = OpStackTypeGet(m_curStackHt - 1);
OpStackTypeSet(m_curStackHt, it);
if (it.IsLargeStruct(&m_interpCeeInfo))
{
sz = it.Size(&m_interpCeeInfo);
void* dest = LargeStructOperandStackPush(sz);
memcpy(dest, OpStackGet<void*>(m_curStackHt - 1), sz);
OpStackSet<void*>(m_curStackHt, dest);
}
else
{
OpStackSet<INT64>(m_curStackHt, OpStackGet<INT64>(m_curStackHt - 1));
}
m_curStackHt++;
break;
case CEE_POP:
_ASSERTE(m_curStackHt > 0);
m_curStackHt--;
it = OpStackTypeGet(m_curStackHt);
if (it.IsLargeStruct(&m_interpCeeInfo))
{
LargeStructOperandStackPop(it.Size(&m_interpCeeInfo), OpStackGet<void*>(m_curStackHt));
}
break;
case CEE_JMP:
*pJmpCallToken = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));
*pDoJmpCall = true;
goto ExitEvalLoop;
case CEE_CALL:
DoCall(/*virtualCall*/false);
#if INTERP_TRACING
if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
{
fprintf(GetLogFile(), " Returning to method %s, stub num %d.\n", methName, m_methInfo->m_stubNum);
}
#endif // INTERP_TRACING
continue;
case CEE_CALLVIRT:
DoCall(/*virtualCall*/true);
#if INTERP_TRACING
if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
{
fprintf(GetLogFile(), " Returning to method %s, stub num %d.\n", methName, m_methInfo->m_stubNum);
}
#endif // INTERP_TRACING
continue;
// HARD
case CEE_CALLI:
CallI();
continue;
case CEE_RET:
if (m_methInfo->m_returnType == CORINFO_TYPE_VOID)
{
_ASSERTE(m_curStackHt == 0);
}
else
{
_ASSERTE(m_curStackHt == 1);
InterpreterType retValIt = OpStackTypeGet(0);
bool looseInt = s_InterpreterLooseRules &&
CorInfoTypeIsIntegral(m_methInfo->m_returnType) &&
(CorInfoTypeIsIntegral(retValIt.ToCorInfoType()) || CorInfoTypeIsPointer(retValIt.ToCorInfoType())) &&
(m_methInfo->m_returnType != retValIt.ToCorInfoType());
bool looseFloat = s_InterpreterLooseRules &&
CorInfoTypeIsFloatingPoint(m_methInfo->m_returnType) &&
CorInfoTypeIsFloatingPoint(retValIt.ToCorInfoType()) &&
(m_methInfo->m_returnType != retValIt.ToCorInfoType());
// Make sure that the return value "matches" (which allows certain relaxations) the declared return type.
_ASSERTE((m_methInfo->m_returnType == CORINFO_TYPE_VALUECLASS && retValIt.ToCorInfoType() == CORINFO_TYPE_VALUECLASS) ||
(m_methInfo->m_returnType == CORINFO_TYPE_REFANY && retValIt.ToCorInfoType() == CORINFO_TYPE_VALUECLASS) ||
(m_methInfo->m_returnType == CORINFO_TYPE_REFANY && retValIt.ToCorInfoType() == CORINFO_TYPE_REFANY) ||
(looseInt || looseFloat) ||
InterpreterType(m_methInfo->m_returnType).StackNormalize().Matches(retValIt, &m_interpCeeInfo));
size_t sz = retValIt.Size(&m_interpCeeInfo);
#if defined(FEATURE_HFA)
CorInfoHFAElemType cit = CORINFO_HFA_ELEM_NONE;
{
GCX_PREEMP();
if(m_methInfo->m_returnType == CORINFO_TYPE_VALUECLASS)
cit = m_interpCeeInfo.getHFAType(retValIt.ToClassHandle());
}
#endif
if (m_methInfo->GetFlag<InterpreterMethodInfo::Flag_hasRetBuffArg>())
{
_ASSERTE((m_methInfo->m_returnType == CORINFO_TYPE_VALUECLASS && retValIt.ToCorInfoType() == CORINFO_TYPE_VALUECLASS) ||
(m_methInfo->m_returnType == CORINFO_TYPE_REFANY && retValIt.ToCorInfoType() == CORINFO_TYPE_VALUECLASS) ||
(m_methInfo->m_returnType == CORINFO_TYPE_REFANY && retValIt.ToCorInfoType() == CORINFO_TYPE_REFANY));
if (retValIt.ToCorInfoType() == CORINFO_TYPE_REFANY)
{
InterpreterType typedRefIT = GetTypedRefIT(&m_interpCeeInfo);
TypedByRef* ptr = OpStackGet<TypedByRef*>(0);
*((TypedByRef*) m_retBufArg) = *ptr;
}
else if (retValIt.IsLargeStruct(&m_interpCeeInfo))
{
MethodTable* clsMt = GetMethodTableFromClsHnd(retValIt.ToClassHandle());
// The ostack value is a pointer to the struct value.
CopyValueClassUnchecked(m_retBufArg, OpStackGet<void*>(0), clsMt);
}
else
{
MethodTable* clsMt = GetMethodTableFromClsHnd(retValIt.ToClassHandle());
// The ostack value *is* the struct value.
CopyValueClassUnchecked(m_retBufArg, OpStackGetAddr(0, sz), clsMt);
}
}
#if defined(FEATURE_HFA)
// Is it an HFA?
else if (m_methInfo->m_returnType == CORINFO_TYPE_VALUECLASS
&& (cit != CORINFO_HFA_ELEM_NONE)
&& (MetaSig(reinterpret_cast<MethodDesc*>(m_methInfo->m_method)).GetCallingConventionInfo() & CORINFO_CALLCONV_VARARG) == 0)
{
if (retValIt.IsLargeStruct(&m_interpCeeInfo))
{
// The ostack value is a pointer to the struct value.
memcpy(GetHFARetBuffAddr(static_cast<unsigned>(sz)), OpStackGet<void*>(0), sz);
}
else
{
// The ostack value *is* the struct value.
memcpy(GetHFARetBuffAddr(static_cast<unsigned>(sz)), OpStackGetAddr(0, sz), sz);
}
}
#elif defined(UNIX_AMD64_ABI)
// Is it an struct contained in $rax and $rdx
else if (m_methInfo->m_returnType == CORINFO_TYPE_VALUECLASS
&& sz == 16)
{
//The Fixed Two slot return buffer address
memcpy(m_ilArgs-16, OpStackGet<void*>(0), sz);
}
#elif defined(TARGET_RISCV64)
// Is it an struct contained in two slots
else if (m_methInfo->m_returnType == CORINFO_TYPE_VALUECLASS
&& sz == 16)
{
//The Fixed Two slot return buffer address
memcpy(m_ilArgs-32, OpStackGet<void*>(0), sz);
}
#endif
else if (CorInfoTypeIsFloatingPoint(m_methInfo->m_returnType) &&
CorInfoTypeIsFloatingPoint(retValIt.ToCorInfoType()))
{
double val = (sz <= sizeof(INT32)) ? OpStackGet<float>(0) : OpStackGet<double>(0);
if (m_methInfo->m_returnType == CORINFO_TYPE_DOUBLE)
{
memcpy(retVal, &val, sizeof(double));
}
else
{
float val2 = (float) val;
memcpy(retVal, &val2, sizeof(float));
}
}
else
{
if (sz <= sizeof(INT32))
{
*retVal = OpStackGet<INT32>(0);
}
else
{
// If looseInt is true, we are relying on auto-downcast in case *retVal
// is small (but this is guaranteed not to happen by def'n of ARG_SLOT.)
//
// Note structs of size 5, 6, 7 may be returned as 8 byte ints.
_ASSERTE(sz <= sizeof(INT64));
*retVal = OpStackGet<INT64>(0);
}
}
}
#if INTERP_PROFILE
// We're not capturing instructions executed in a method that terminates via exception,
// but that's OK...
m_methInfo->RecordExecInstrs(instrs);
#endif
#if INTERP_TRACING
// We keep this live until we leave.
delete methName;
#endif // INTERP_TRACING
#if INTERP_ILCYCLE_PROFILE
// Finish off accounting for the "RET" before we return
UpdateCycleCount();
#endif // INTERP_ILCYCLE_PROFILE
goto ExitEvalLoop;
case CEE_BR_S:
m_ILCodePtr++;
offsetc = *m_ILCodePtr;
// The offset is wrt the beginning of the following instruction, so the +1 is to get to that
// m_ILCodePtr value before adding the offset.
ExecuteBranch(m_ILCodePtr + offsetc + 1);
continue; // Skip the default m_ILCodePtr++ at bottom of loop.
case CEE_LEAVE_S:
// LEAVE empties the operand stack.
m_curStackHt = 0;
m_largeStructOperandStackHt = 0;
offsetc = getI1(m_ILCodePtr + 1);
{
// The offset is wrt the beginning of the following instruction, so the +2 is to get to that
// m_ILCodePtr value before adding the offset.
BYTE* leaveTarget = m_ILCodePtr + offsetc + 2;
unsigned leaveOffset = CurOffset();
m_leaveInfoStack.Push(LeaveInfo(leaveOffset, leaveTarget));
if (!SearchForCoveringFinally())
{
m_leaveInfoStack.Pop();
ExecuteBranch(leaveTarget);
}
}
continue; // Skip the default m_ILCodePtr++ at bottom of loop.
// Abstract the next pair out to something common with templates.
case CEE_BRFALSE_S:
BrOnValue<false, 1>();
continue;
case CEE_BRTRUE_S:
BrOnValue<true, 1>();
continue;
case CEE_BEQ_S:
BrOnComparison<CO_EQ, false, 1>();
continue;
case CEE_BGE_S:
_ASSERTE(m_curStackHt >= 2);
// ECMA spec gives different semantics for different operand types:
switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
{
case CORINFO_TYPE_FLOAT:
case CORINFO_TYPE_DOUBLE:
BrOnComparison<CO_LT_UN, true, 1>();
break;
default:
BrOnComparison<CO_LT, true, 1>();
break;
}
continue;
case CEE_BGT_S:
BrOnComparison<CO_GT, false, 1>();
continue;
case CEE_BLE_S:
_ASSERTE(m_curStackHt >= 2);
// ECMA spec gives different semantics for different operand types:
switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
{
case CORINFO_TYPE_FLOAT:
case CORINFO_TYPE_DOUBLE:
BrOnComparison<CO_GT_UN, true, 1>();
break;
default:
BrOnComparison<CO_GT, true, 1>();
break;
}
continue;
case CEE_BLT_S:
BrOnComparison<CO_LT, false, 1>();
continue;
case CEE_BNE_UN_S:
BrOnComparison<CO_EQ, true, 1>();
continue;
case CEE_BGE_UN_S:
_ASSERTE(m_curStackHt >= 2);
// ECMA spec gives different semantics for different operand types:
switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
{
case CORINFO_TYPE_FLOAT:
case CORINFO_TYPE_DOUBLE:
BrOnComparison<CO_LT, true, 1>();
break;
default:
BrOnComparison<CO_LT_UN, true, 1>();
break;
}
continue;
case CEE_BGT_UN_S:
BrOnComparison<CO_GT_UN, false, 1>();
continue;
case CEE_BLE_UN_S:
_ASSERTE(m_curStackHt >= 2);
// ECMA spec gives different semantics for different operand types:
switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
{
case CORINFO_TYPE_FLOAT:
case CORINFO_TYPE_DOUBLE:
BrOnComparison<CO_GT, true, 1>();
break;
default:
BrOnComparison<CO_GT_UN, true, 1>();
break;
}
continue;
case CEE_BLT_UN_S:
BrOnComparison<CO_LT_UN, false, 1>();
continue;
case CEE_BR:
m_ILCodePtr++;
vali = getI4LittleEndian(m_ILCodePtr);
vali += 4; // +4 for the length of the offset.
ExecuteBranch(m_ILCodePtr + vali);
if (vali < 0)
{
// Backwards branch -- enable caching.
BackwardsBranchActions(vali);
}
continue;
case CEE_LEAVE:
// LEAVE empties the operand stack.
m_curStackHt = 0;
m_largeStructOperandStackHt = 0;
vali = getI4LittleEndian(m_ILCodePtr + 1);
{
// The offset is wrt the beginning of the following instruction, so the +5 is to get to that
// m_ILCodePtr value before adding the offset.
BYTE* leaveTarget = m_ILCodePtr + (vali + 5);
unsigned leaveOffset = CurOffset();
m_leaveInfoStack.Push(LeaveInfo(leaveOffset, leaveTarget));
if (!SearchForCoveringFinally())
{
(void)m_leaveInfoStack.Pop();
if (vali < 0)
{
// Backwards branch -- enable caching.
BackwardsBranchActions(vali);
}
ExecuteBranch(leaveTarget);
}
}
continue; // Skip the default m_ILCodePtr++ at bottom of loop.
case CEE_BRFALSE:
BrOnValue<false, 4>();
continue;
case CEE_BRTRUE:
BrOnValue<true, 4>();
continue;
case CEE_BEQ:
BrOnComparison<CO_EQ, false, 4>();
continue;
case CEE_BGE:
_ASSERTE(m_curStackHt >= 2);
// ECMA spec gives different semantics for different operand types:
switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
{
case CORINFO_TYPE_FLOAT:
case CORINFO_TYPE_DOUBLE:
BrOnComparison<CO_LT_UN, true, 4>();
break;
default:
BrOnComparison<CO_LT, true, 4>();
break;
}
continue;
case CEE_BGT:
BrOnComparison<CO_GT, false, 4>();
continue;
case CEE_BLE:
_ASSERTE(m_curStackHt >= 2);
// ECMA spec gives different semantics for different operand types:
switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
{
case CORINFO_TYPE_FLOAT:
case CORINFO_TYPE_DOUBLE:
BrOnComparison<CO_GT_UN, true, 4>();
break;
default:
BrOnComparison<CO_GT, true, 4>();
break;
}
continue;
case CEE_BLT:
BrOnComparison<CO_LT, false, 4>();
continue;
case CEE_BNE_UN:
BrOnComparison<CO_EQ, true, 4>();
continue;
case CEE_BGE_UN:
_ASSERTE(m_curStackHt >= 2);
// ECMA spec gives different semantics for different operand types:
switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
{
case CORINFO_TYPE_FLOAT:
case CORINFO_TYPE_DOUBLE:
BrOnComparison<CO_LT, true, 4>();
break;
default:
BrOnComparison<CO_LT_UN, true, 4>();
break;
}
continue;
case CEE_BGT_UN:
BrOnComparison<CO_GT_UN, false, 4>();
continue;
case CEE_BLE_UN:
_ASSERTE(m_curStackHt >= 2);
// ECMA spec gives different semantics for different operand types:
switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
{
case CORINFO_TYPE_FLOAT:
case CORINFO_TYPE_DOUBLE:
BrOnComparison<CO_GT, true, 4>();
break;
default:
BrOnComparison<CO_GT_UN, true, 4>();
break;
}
continue;
case CEE_BLT_UN:
BrOnComparison<CO_LT_UN, false, 4>();
continue;
case CEE_SWITCH:
{
_ASSERTE(m_curStackHt > 0);
m_curStackHt--;
#if defined(_DEBUG) || defined(HOST_AMD64)
CorInfoType cit = OpStackTypeGet(m_curStackHt).ToCorInfoType();
#endif // _DEBUG || HOST_AMD64
#ifdef _DEBUG
_ASSERTE(cit == CORINFO_TYPE_INT || cit == CORINFO_TYPE_UINT || cit == CORINFO_TYPE_NATIVEINT);
#endif // _DEBUG
#if defined(HOST_AMD64)
UINT32 val = (cit == CORINFO_TYPE_NATIVEINT) ? (INT32) OpStackGet<NativeInt>(m_curStackHt)
: OpStackGet<INT32>(m_curStackHt);
#else
UINT32 val = OpStackGet<INT32>(m_curStackHt);
#endif
UINT32 n = getU4LittleEndian(m_ILCodePtr + 1);
UINT32 instrSize = 1 + (n + 1)*4;
if (val < n)
{
vali = getI4LittleEndian(m_ILCodePtr + (5 + val * 4));
ExecuteBranch(m_ILCodePtr + instrSize + vali);
}
else
{
m_ILCodePtr += instrSize;
}
}
continue;
case CEE_LDIND_I1:
LdIndShort<INT8, /*isUnsigned*/false>();
break;
case CEE_LDIND_U1:
LdIndShort<UINT8, /*isUnsigned*/true>();
break;
case CEE_LDIND_I2:
LdIndShort<INT16, /*isUnsigned*/false>();
break;
case CEE_LDIND_U2:
LdIndShort<UINT16, /*isUnsigned*/true>();
break;
case CEE_LDIND_I4:
LdInd<INT32, CORINFO_TYPE_INT>();
break;
case CEE_LDIND_U4:
LdInd<UINT32, CORINFO_TYPE_INT>();
break;
case CEE_LDIND_I8:
LdInd<INT64, CORINFO_TYPE_LONG>();
break;
case CEE_LDIND_I:
LdInd<NativeInt, CORINFO_TYPE_NATIVEINT>();
break;
case CEE_LDIND_R4:
LdInd<float, CORINFO_TYPE_FLOAT>();
break;
case CEE_LDIND_R8:
LdInd<double, CORINFO_TYPE_DOUBLE>();
break;
case CEE_LDIND_REF:
LdInd<Object*, CORINFO_TYPE_CLASS>();
break;
case CEE_STIND_REF:
StInd_Ref();
break;
case CEE_STIND_I1:
StInd<INT8>();
break;
case CEE_STIND_I2:
StInd<INT16>();
break;
case CEE_STIND_I4:
StInd<INT32>();
break;
case CEE_STIND_I8:
StInd<INT64>();
break;
case CEE_STIND_R4:
StInd<float>();
break;
case CEE_STIND_R8:
StInd<double>();
break;
case CEE_ADD:
BinaryArithOp<BA_Add>();
m_ILCodePtr++;
continue;
case CEE_SUB:
BinaryArithOp<BA_Sub>();
break;
case CEE_MUL:
BinaryArithOp<BA_Mul>();
break;
case CEE_DIV:
BinaryArithOp<BA_Div>();
break;
case CEE_DIV_UN:
BinaryIntOp<BIO_DivUn>();
break;
case CEE_REM:
BinaryArithOp<BA_Rem>();
break;
case CEE_REM_UN:
BinaryIntOp<BIO_RemUn>();
break;
case CEE_AND:
BinaryIntOp<BIO_And>();
break;
case CEE_OR:
BinaryIntOp<BIO_Or>();
break;
case CEE_XOR:
BinaryIntOp<BIO_Xor>();
break;
case CEE_SHL:
ShiftOp<CEE_SHL>();
break;
case CEE_SHR:
ShiftOp<CEE_SHR>();
break;
case CEE_SHR_UN:
ShiftOp<CEE_SHR_UN>();
break;
case CEE_NEG:
Neg();
break;
case CEE_NOT:
Not();
break;
case CEE_CONV_I1:
Conv<INT8, /*TIsUnsigned*/false, /*TCanHoldPtr*/false, /*TIsShort*/true, CORINFO_TYPE_INT>();
break;
case CEE_CONV_I2:
Conv<INT16, /*TIsUnsigned*/false, /*TCanHoldPtr*/false, /*TIsShort*/true, CORINFO_TYPE_INT>();
break;
case CEE_CONV_I4:
Conv<INT32, /*TIsUnsigned*/false, /*TCanHoldPtr*/false, /*TIsShort*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_I8:
Conv<INT64, /*TIsUnsigned*/false, /*TCanHoldPtr*/true, /*TIsShort*/false, CORINFO_TYPE_LONG>();
break;
case CEE_CONV_R4:
Conv<float, /*TIsUnsigned*/false, /*TCanHoldPtr*/false, /*TIsShort*/false, CORINFO_TYPE_FLOAT>();
break;
case CEE_CONV_R8:
Conv<double, /*TIsUnsigned*/false, /*TCanHoldPtr*/false, /*TIsShort*/false, CORINFO_TYPE_DOUBLE>();
break;
case CEE_CONV_U4:
Conv<UINT32, /*TIsUnsigned*/true, /*TCanHoldPtr*/false, /*TIsShort*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_U8:
Conv<UINT64, /*TIsUnsigned*/true, /*TCanHoldPtr*/true, /*TIsShort*/false, CORINFO_TYPE_LONG>();
break;
case CEE_CPOBJ:
CpObj();
continue;
case CEE_LDOBJ:
LdObj();
continue;
case CEE_LDSTR:
LdStr();
continue;
case CEE_NEWOBJ:
NewObj();
#if INTERP_TRACING
if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
{
fprintf(GetLogFile(), " Returning to method %s, stub num %d.\n", methName, m_methInfo->m_stubNum);
}
#endif // INTERP_TRACING
continue;
case CEE_CASTCLASS:
CastClass();
continue;
case CEE_ISINST:
IsInst();
continue;
case CEE_CONV_R_UN:
ConvRUn();
break;
case CEE_UNBOX:
Unbox();
continue;
case CEE_THROW:
Throw();
break;
case CEE_LDFLD:
LdFld();
continue;
case CEE_LDFLDA:
LdFldA();
continue;
case CEE_STFLD:
StFld();
continue;
case CEE_LDSFLD:
LdSFld();
continue;
case CEE_LDSFLDA:
LdSFldA();
continue;
case CEE_STSFLD:
StSFld();
continue;
case CEE_STOBJ:
StObj();
continue;
case CEE_CONV_OVF_I1_UN:
ConvOvfUn<INT8, SCHAR_MIN, SCHAR_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_I2_UN:
ConvOvfUn<INT16, SHRT_MIN, SHRT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_I4_UN:
ConvOvfUn<INT32, INT_MIN, INT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_I8_UN:
ConvOvfUn<INT64, _I64_MIN, _I64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_LONG>();
break;
case CEE_CONV_OVF_U1_UN:
ConvOvfUn<UINT8, 0, UCHAR_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_U2_UN:
ConvOvfUn<UINT16, 0, USHRT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_U4_UN:
ConvOvfUn<UINT32, 0, UINT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_U8_UN:
ConvOvfUn<UINT64, 0, _UI64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_LONG>();
break;
case CEE_CONV_OVF_I_UN:
if (sizeof(NativeInt) == 4)
{
ConvOvfUn<NativeInt, INT_MIN, INT_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
}
else
{
_ASSERTE(sizeof(NativeInt) == 8);
ConvOvfUn<NativeInt, _I64_MIN, _I64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
}
break;
case CEE_CONV_OVF_U_UN:
if (sizeof(NativeUInt) == 4)
{
ConvOvfUn<NativeUInt, 0, UINT_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
}
else
{
_ASSERTE(sizeof(NativeUInt) == 8);
ConvOvfUn<NativeUInt, 0, _UI64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
}
break;
case CEE_BOX:
Box();
continue;
case CEE_NEWARR:
NewArr();
continue;
case CEE_LDLEN:
LdLen();
break;
case CEE_LDELEMA:
LdElem</*takeAddr*/true>();
continue;
case CEE_LDELEM_I1:
LdElemWithType<INT8, false, CORINFO_TYPE_INT>();
break;
case CEE_LDELEM_U1:
LdElemWithType<UINT8, false, CORINFO_TYPE_INT>();
break;
case CEE_LDELEM_I2:
LdElemWithType<INT16, false, CORINFO_TYPE_INT>();
break;
case CEE_LDELEM_U2:
LdElemWithType<UINT16, false, CORINFO_TYPE_INT>();
break;
case CEE_LDELEM_I4:
LdElemWithType<INT32, false, CORINFO_TYPE_INT>();
break;
case CEE_LDELEM_U4:
LdElemWithType<UINT32, false, CORINFO_TYPE_INT>();
break;
case CEE_LDELEM_I8:
LdElemWithType<INT64, false, CORINFO_TYPE_LONG>();
break;
// Note that the ECMA spec defines a "LDELEM_U8", but it is the same instruction number as LDELEM_I8 (since
// when loading to the widest width, signed/unsigned doesn't matter).
case CEE_LDELEM_I:
LdElemWithType<NativeInt, false, CORINFO_TYPE_NATIVEINT>();
break;
case CEE_LDELEM_R4:
LdElemWithType<float, false, CORINFO_TYPE_FLOAT>();
break;
case CEE_LDELEM_R8:
LdElemWithType<double, false, CORINFO_TYPE_DOUBLE>();
break;
case CEE_LDELEM_REF:
LdElemWithType<Object*, true, CORINFO_TYPE_CLASS>();
break;
case CEE_STELEM_I:
StElemWithType<NativeInt, false>();
break;
case CEE_STELEM_I1:
StElemWithType<INT8, false>();
break;
case CEE_STELEM_I2:
StElemWithType<INT16, false>();
break;
case CEE_STELEM_I4:
StElemWithType<INT32, false>();
break;
case CEE_STELEM_I8:
StElemWithType<INT64, false>();
break;
case CEE_STELEM_R4:
StElemWithType<float, false>();
break;
case CEE_STELEM_R8:
StElemWithType<double, false>();
break;
case CEE_STELEM_REF:
StElemWithType<Object*, true>();
break;
case CEE_LDELEM:
LdElem</*takeAddr*/false>();
continue;
case CEE_STELEM:
StElem();
continue;
case CEE_UNBOX_ANY:
UnboxAny();
continue;
case CEE_CONV_OVF_I1:
ConvOvf<INT8, SCHAR_MIN, SCHAR_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_U1:
ConvOvf<UINT8, 0, UCHAR_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_I2:
ConvOvf<INT16, SHRT_MIN, SHRT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_U2:
ConvOvf<UINT16, 0, USHRT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_I4:
ConvOvf<INT32, INT_MIN, INT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_U4:
ConvOvf<UINT32, 0, UINT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_I8:
ConvOvf<INT64, _I64_MIN, _I64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_LONG>();
break;
case CEE_CONV_OVF_U8:
ConvOvf<UINT64, 0, _UI64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_LONG>();
break;
case CEE_REFANYVAL:
RefanyVal();
continue;
case CEE_CKFINITE:
CkFinite();
break;
case CEE_MKREFANY:
MkRefany();
continue;
case CEE_LDTOKEN:
LdToken();
continue;
case CEE_CONV_U2:
Conv<UINT16, /*TIsUnsigned*/true, /*TCanHoldPtr*/false, /*TIsShort*/true, CORINFO_TYPE_INT>();
break;
case CEE_CONV_U1:
Conv<UINT8, /*TIsUnsigned*/true, /*TCanHoldPtr*/false, /*TIsShort*/true, CORINFO_TYPE_INT>();
break;
case CEE_CONV_I:
Conv<NativeInt, /*TIsUnsigned*/false, /*TCanHoldPtr*/true, /*TIsShort*/false, CORINFO_TYPE_NATIVEINT>();
break;
case CEE_CONV_OVF_I:
if (sizeof(NativeInt) == 4)
{
ConvOvf<NativeInt, INT_MIN, INT_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
}
else
{
_ASSERTE(sizeof(NativeInt) == 8);
ConvOvf<NativeInt, _I64_MIN, _I64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
}
break;
case CEE_CONV_OVF_U:
if (sizeof(NativeUInt) == 4)
{
ConvOvf<NativeUInt, 0, UINT_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
}
else
{
_ASSERTE(sizeof(NativeUInt) == 8);
ConvOvf<NativeUInt, 0, _UI64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
}
break;
case CEE_ADD_OVF:
BinaryArithOvfOp<BA_Add, /*asUnsigned*/false>();
break;
case CEE_ADD_OVF_UN:
BinaryArithOvfOp<BA_Add, /*asUnsigned*/true>();
break;
case CEE_MUL_OVF:
BinaryArithOvfOp<BA_Mul, /*asUnsigned*/false>();
break;
case CEE_MUL_OVF_UN:
BinaryArithOvfOp<BA_Mul, /*asUnsigned*/true>();
break;
case CEE_SUB_OVF:
BinaryArithOvfOp<BA_Sub, /*asUnsigned*/false>();
break;
case CEE_SUB_OVF_UN:
BinaryArithOvfOp<BA_Sub, /*asUnsigned*/true>();
break;
case CEE_ENDFINALLY:
// We have just ended a finally.
// If we were called during exception dispatch,
// rethrow the exception on our way out.
if (m_leaveInfoStack.IsEmpty())
{
Object* finallyException = NULL;
{
GCX_FORBID();
_ASSERTE(m_inFlightException != NULL);
finallyException = m_inFlightException;
INTERPLOG("endfinally handling for %s, %p, %p\n", methName, m_methInfo, finallyException);
m_inFlightException = NULL;
}
COMPlusThrow(ObjectToOBJECTREF(finallyException));
UNREACHABLE();
}
// Otherwise, see if there's another finally block to
// execute as part of processing the current LEAVE...
else if (!SearchForCoveringFinally())
{
// No, there isn't -- go to the leave target.
_ASSERTE(!m_leaveInfoStack.IsEmpty());
LeaveInfo li = m_leaveInfoStack.Pop();
ExecuteBranch(li.m_target);
}
// Yes, there, is, and SearchForCoveringFinally set us up to start executing it.
continue; // Skip the default m_ILCodePtr++ at bottom of loop.
case CEE_STIND_I:
StInd<NativeInt>();
break;
case CEE_CONV_U:
Conv<NativeUInt, /*TIsUnsigned*/true, /*TCanHoldPtr*/true, /*TIsShort*/false, CORINFO_TYPE_NATIVEINT>();
break;
case CEE_PREFIX7:
NYI_INTERP("Unimplemented opcode: CEE_PREFIX7");
break;
case CEE_PREFIX6:
NYI_INTERP("Unimplemented opcode: CEE_PREFIX6");
break;
case CEE_PREFIX5:
NYI_INTERP("Unimplemented opcode: CEE_PREFIX5");
break;
case CEE_PREFIX4:
NYI_INTERP("Unimplemented opcode: CEE_PREFIX4");
break;
case CEE_PREFIX3:
NYI_INTERP("Unimplemented opcode: CEE_PREFIX3");
break;
case CEE_PREFIX2:
NYI_INTERP("Unimplemented opcode: CEE_PREFIX2");
break;
case CEE_PREFIX1:
// This is the prefix for all the 2-byte opcodes.
// Figure out the second byte of the 2-byte opcode.
ops = *(m_ILCodePtr + 1);
#if INTERP_ILINSTR_PROFILE
// Take one away from PREFIX1, which we won't count.
InterlockedDecrement(&s_ILInstrExecs[CEE_PREFIX1]);
// Credit instead to the 2-byte instruction index.
InterlockedIncrement(&s_ILInstr2ByteExecs[ops]);
#endif // INTERP_ILINSTR_PROFILE
switch (ops)
{
case TWOBYTE_CEE_ARGLIST:
// NYI_INTERP("Unimplemented opcode: TWOBYTE_CEE_ARGLIST");
_ASSERTE(m_methInfo->m_varArgHandleArgNum != NO_VA_ARGNUM);
LdArgA(m_methInfo->m_varArgHandleArgNum);
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_CEQ:
CompareOp<CO_EQ>();
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_CGT:
CompareOp<CO_GT>();
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_CGT_UN:
CompareOp<CO_GT_UN>();
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_CLT:
CompareOp<CO_LT>();
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_CLT_UN:
CompareOp<CO_LT_UN>();
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_LDARG:
m_ILCodePtr += 2;
argNums = getU2LittleEndian(m_ILCodePtr);
LdArg(argNums);
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_LDARGA:
m_ILCodePtr += 2;
argNums = getU2LittleEndian(m_ILCodePtr);
LdArgA(argNums);
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_STARG:
m_ILCodePtr += 2;
argNums = getU2LittleEndian(m_ILCodePtr);
StArg(argNums);
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_LDLOC:
m_ILCodePtr += 2;
argNums = getU2LittleEndian(m_ILCodePtr);
LdLoc(argNums);
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_LDLOCA:
m_ILCodePtr += 2;
argNums = getU2LittleEndian(m_ILCodePtr);
LdLocA(argNums);
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_STLOC:
m_ILCodePtr += 2;
argNums = getU2LittleEndian(m_ILCodePtr);
StLoc(argNums);
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_CONSTRAINED:
RecordConstrainedCall();
break;
case TWOBYTE_CEE_VOLATILE:
// Set a flag that causes a memory barrier to be associated with the next load or store.
m_volatileFlag = true;
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_LDFTN:
LdFtn();
break;
case TWOBYTE_CEE_INITOBJ:
InitObj();
break;
case TWOBYTE_CEE_LOCALLOC:
LocAlloc();
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_LDVIRTFTN:
LdVirtFtn();
break;
case TWOBYTE_CEE_SIZEOF:
Sizeof();
break;
case TWOBYTE_CEE_RETHROW:
Rethrow();
break;
case TWOBYTE_CEE_READONLY:
m_readonlyFlag = true;
m_ILCodePtr += 2;
// A comment in importer.cpp indicates that READONLY may also apply to calls. We'll see.
_ASSERTE_MSG(*m_ILCodePtr == CEE_LDELEMA, "According to the ECMA spec, READONLY may only precede LDELEMA");
break;
case TWOBYTE_CEE_INITBLK:
InitBlk();
break;
case TWOBYTE_CEE_CPBLK:
CpBlk();
break;
case TWOBYTE_CEE_ENDFILTER:
EndFilter();
break;
case TWOBYTE_CEE_UNALIGNED:
// Nothing to do here.
m_ILCodePtr += 3;
break;
case TWOBYTE_CEE_TAILCALL:
// TODO: Needs revisiting when implementing tail call.
// NYI_INTERP("Unimplemented opcode: TWOBYTE_CEE_TAILCALL");
m_ILCodePtr += 2;
break;
case TWOBYTE_CEE_REFANYTYPE:
RefanyType();
break;
default:
UNREACHABLE();
break;
}
continue;
case CEE_PREFIXREF:
NYI_INTERP("Unimplemented opcode: CEE_PREFIXREF");
m_ILCodePtr++;
continue;
default:
UNREACHABLE();
continue;
}
m_ILCodePtr++;
}
ExitEvalLoop:;
INTERPLOG("DONE %d, %s\n", m_methInfo->m_stubNum, m_methInfo->m_methName);
}
EX_CATCH
{
INTERPLOG("EXCEPTION %d (throw), %s\n", m_methInfo->m_stubNum, m_methInfo->m_methName);
bool handleException = false;
OBJECTREF orThrowable = NULL;
GCX_COOP_NO_DTOR();
orThrowable = GET_THROWABLE();
if (m_filterNextScan != 0)
{
// We are in the middle of a filter scan and an exception is thrown inside
// a filter. We are supposed to swallow it and assume the filter did not
// handle the exception.
m_curStackHt = 0;
m_largeStructOperandStackHt = 0;
LdIcon(0);
EndFilter();
handleException = true;
}
else
{
// orThrowable must be protected. MethodHandlesException() will place orThrowable
// into the operand stack (a permanently protected area) if it returns true.
GCPROTECT_BEGIN(orThrowable);
handleException = MethodHandlesException(orThrowable);
GCPROTECT_END();
}
if (handleException)
{
GetThread()->SafeSetThrowables(orThrowable
DEBUG_ARG(ThreadExceptionState::STEC_CurrentTrackerEqualNullOkForInterpreter));
goto EvalLoop;
}
else
{
INTERPLOG("EXCEPTION %d (rethrow), %s\n", m_methInfo->m_stubNum, m_methInfo->m_methName);
EX_RETHROW;
}
}
EX_END_CATCH(RethrowTransientExceptions)
}
#ifdef _MSC_VER
#pragma optimize("", on)
#endif
void Interpreter::EndFilter()
{
unsigned handles = OpStackGet<unsigned>(0);
// If the filter decides to handle the exception, then go to the handler offset.
if (handles)
{
// We decided to handle the exception, so give all EH entries a chance to
// handle future exceptions. Clear scan.
m_filterNextScan = 0;
ExecuteBranch(m_methInfo->m_ILCode + m_filterHandlerOffset);
}
// The filter decided not to handle the exception, ask if there is some other filter
// lined up to try to handle it or some other catch/finally handlers will handle it.
// If no one handles the exception, rethrow and be done with it.
else
{
bool handlesEx = false;
{
OBJECTREF orThrowable = ObjectToOBJECTREF(m_inFlightException);
GCPROTECT_BEGIN(orThrowable);
handlesEx = MethodHandlesException(orThrowable);
GCPROTECT_END();
}
if (!handlesEx)
{
// Just clear scan before rethrowing to give any EH entry a chance to handle
// the "rethrow".
m_filterNextScan = 0;
Object* filterException = NULL;
{
GCX_FORBID();
_ASSERTE(m_inFlightException != NULL);
filterException = m_inFlightException;
INTERPLOG("endfilter handling for %s, %p, %p\n", m_methInfo->m_methName, m_methInfo, filterException);
m_inFlightException = NULL;
}
COMPlusThrow(ObjectToOBJECTREF(filterException));
UNREACHABLE();
}
else
{
// Let it do another round of filter:end-filter or handler block.
// During the next end filter, we will reuse m_filterNextScan and
// continue searching where we left off. Note however, while searching,
// any of the filters could throw an exception. But this is supposed to
// be swallowed and endfilter should be called with a value of 0 on the
// stack.
}
}
}
bool Interpreter::MethodHandlesException(OBJECTREF orThrowable)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
bool handlesEx = false;
if (orThrowable != NULL)
{
// Don't catch ThreadAbort and other uncatchable exceptions
if (!IsUncatchable(&orThrowable))
{
// Does the current method catch this? The clauses are defined by offsets, so get that.
// However, if we are in the middle of a filter scan, make sure we get the offset of the
// excepting code, rather than the offset of the filter body.
DWORD curOffset = (m_filterNextScan != 0) ? m_filterExcILOffset : CurOffset();
TypeHandle orThrowableTH = TypeHandle(orThrowable->GetMethodTable());
GCPROTECT_BEGIN(orThrowable);
GCX_PREEMP();
// Perform a filter scan or regular walk of the EH Table. Filter scan is performed when
// we are evaluating a series of filters to handle the exception until the first handler
// (filter's or otherwise) that will handle the exception.
for (unsigned XTnum = m_filterNextScan; XTnum < m_methInfo->m_ehClauseCount; XTnum++)
{
CORINFO_EH_CLAUSE clause;
m_interpCeeInfo.getEHinfo(m_methInfo->m_method, XTnum, &clause);
_ASSERTE(clause.HandlerLength != (unsigned)-1); // @DEPRECATED
// First, is the current offset in the try block?
if (clause.TryOffset <= curOffset && curOffset < clause.TryOffset + clause.TryLength)
{
unsigned handlerOffset = 0;
// CORINFO_EH_CLAUSE_NONE represents 'catch' blocks
if (clause.Flags == CORINFO_EH_CLAUSE_NONE)
{
// Now, does the catch block handle the thrown exception type?
CORINFO_CLASS_HANDLE excType = FindClass(clause.ClassToken InterpTracingArg(RTK_CheckHandlesException));
if (ExceptionIsOfRightType(TypeHandle::FromPtr(excType), orThrowableTH))
{
GCX_COOP();
// Push the exception object onto the operand stack.
OpStackSet<OBJECTREF>(0, orThrowable);
OpStackTypeSet(0, InterpreterType(CORINFO_TYPE_CLASS));
m_curStackHt = 1;
m_largeStructOperandStackHt = 0;
handlerOffset = clause.HandlerOffset;
handlesEx = true;
m_filterNextScan = 0;
}
else
{
GCX_COOP();
// Handle a wrapped exception.
OBJECTREF orUnwrapped = PossiblyUnwrapThrowable(orThrowable, GetMethodDesc()->GetAssembly());
if (ExceptionIsOfRightType(TypeHandle::FromPtr(excType), orUnwrapped->GetTypeHandle()))
{
// Push the exception object onto the operand stack.
OpStackSet<OBJECTREF>(0, orUnwrapped);
OpStackTypeSet(0, InterpreterType(CORINFO_TYPE_CLASS));
m_curStackHt = 1;
m_largeStructOperandStackHt = 0;
handlerOffset = clause.HandlerOffset;
handlesEx = true;
m_filterNextScan = 0;
}
}
}
else if (clause.Flags == CORINFO_EH_CLAUSE_FILTER)
{
GCX_COOP();
// Push the exception object onto the operand stack.
OpStackSet<OBJECTREF>(0, orThrowable);
OpStackTypeSet(0, InterpreterType(CORINFO_TYPE_CLASS));
m_curStackHt = 1;
m_largeStructOperandStackHt = 0;
handlerOffset = clause.FilterOffset;
m_inFlightException = OBJECTREFToObject(orThrowable);
handlesEx = true;
m_filterHandlerOffset = clause.HandlerOffset;
m_filterNextScan = XTnum + 1;
m_filterExcILOffset = curOffset;
}
else if (clause.Flags == CORINFO_EH_CLAUSE_FAULT ||
clause.Flags == CORINFO_EH_CLAUSE_FINALLY)
{
GCX_COOP();
// Save the exception object to rethrow.
m_inFlightException = OBJECTREFToObject(orThrowable);
// Empty the operand stack.
m_curStackHt = 0;
m_largeStructOperandStackHt = 0;
handlerOffset = clause.HandlerOffset;
handlesEx = true;
m_filterNextScan = 0;
}
// Reset the interpreter loop in preparation of calling the handler.
if (handlesEx)
{
// Set the IL offset of the handler.
ExecuteBranch(m_methInfo->m_ILCode + handlerOffset);
// If an exception occurs while attempting to leave a protected scope,
// we empty the 'leave' info stack upon entering the handler.
while (!m_leaveInfoStack.IsEmpty())
{
m_leaveInfoStack.Pop();
}
// Some things are set up before a call, and must be cleared on an exception caught be the caller.
// A method that returns a struct allocates local space for the return value, and "registers" that
// space and the type so that it's scanned if a GC happens. "Unregister" it if we throw an exception
// in the call, and handle it in the caller. (If it's not handled by the caller, the Interpreter is
// deallocated, so it's value doesn't matter.)
m_structRetValITPtr = NULL;
m_callThisArg = NULL;
m_argsSize = 0;
break;
}
}
}
GCPROTECT_END();
}
if (!handlesEx)
{
DoMonitorExitWork();
}
}
return handlesEx;
}
static unsigned OpFormatExtraSize(opcode_format_t format) {
switch (format)
{
case InlineNone:
return 0;
case InlineVar:
return 2;
case InlineI:
case InlineBrTarget:
case InlineMethod:
case InlineField:
case InlineType:
case InlineString:
case InlineSig:
case InlineRVA:
case InlineTok:
case ShortInlineR:
return 4;
case InlineR:
case InlineI8:
return 8;
case InlineSwitch:
return 0; // We'll handle this specially.
case ShortInlineVar:
case ShortInlineI:
case ShortInlineBrTarget:
return 1;
default:
_ASSERTE(false);
return 0;
}
}
static unsigned opSizes1Byte[CEE_COUNT];
static bool opSizes1ByteInit = false;
static void OpSizes1ByteInit()
{
if (opSizes1ByteInit) return;
#define OPDEF(name, stringname, stackpop, stackpush, params, kind, len, byte1, byte2, ctrl) \
opSizes1Byte[name] = len + OpFormatExtraSize(params);
#include "opcode.def"
#undef OPDEF
opSizes1ByteInit = true;
};
// static
bool Interpreter::MethodMayHaveLoop(BYTE* ilCode, unsigned codeSize)
{
OpSizes1ByteInit();
int delta;
BYTE* ilCodeLim = ilCode + codeSize;
while (ilCode < ilCodeLim)
{
unsigned op = *ilCode;
switch (op)
{
case CEE_BR_S: case CEE_BRFALSE_S: case CEE_BRTRUE_S:
case CEE_BEQ_S: case CEE_BGE_S: case CEE_BGT_S: case CEE_BLE_S: case CEE_BLT_S:
case CEE_BNE_UN_S: case CEE_BGE_UN_S: case CEE_BGT_UN_S: case CEE_BLE_UN_S: case CEE_BLT_UN_S:
case CEE_LEAVE_S:
delta = getI1(ilCode + 1);
if (delta < 0) return true;
ilCode += 2;
break;
case CEE_BR: case CEE_BRFALSE: case CEE_BRTRUE:
case CEE_BEQ: case CEE_BGE: case CEE_BGT: case CEE_BLE: case CEE_BLT:
case CEE_BNE_UN: case CEE_BGE_UN: case CEE_BGT_UN: case CEE_BLE_UN: case CEE_BLT_UN:
case CEE_LEAVE:
delta = getI4LittleEndian(ilCode + 1);
if (delta < 0) return true;
ilCode += 5;
break;
case CEE_SWITCH:
{
UINT32 n = getU4LittleEndian(ilCode + 1);
UINT32 instrSize = 1 + (n + 1)*4;
for (unsigned i = 0; i < n; i++) {
delta = getI4LittleEndian(ilCode + (5 + i * 4));
if (delta < 0) return true;
}
ilCode += instrSize;
break;
}
case CEE_PREFIX1:
op = *(ilCode + 1) + 0x100;
_ASSERTE(op < CEE_COUNT); // Bounds check for below.
// deliberate fall-through here.
__fallthrough;
default:
// For the rest of the 1-byte instructions, we'll use a table-driven approach.
ilCode += opSizes1Byte[op];
break;
}
}
return false;
}
void Interpreter::BackwardsBranchActions(int offset)
{
// TODO: Figure out how to do a GC poll.
}
bool Interpreter::SearchForCoveringFinally()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_ANY;
} CONTRACTL_END;
_ASSERTE_MSG(!m_leaveInfoStack.IsEmpty(), "precondition");
LeaveInfo& li = m_leaveInfoStack.PeekRef();
GCX_PREEMP();
for (unsigned XTnum = li.m_nextEHIndex; XTnum < m_methInfo->m_ehClauseCount; XTnum++)
{
CORINFO_EH_CLAUSE clause;
m_interpCeeInfo.getEHinfo(m_methInfo->m_method, XTnum, &clause);
_ASSERTE(clause.HandlerLength != (unsigned)-1); // @DEPRECATED
// First, is the offset of the leave instruction in the try block?
unsigned tryEndOffset = clause.TryOffset + clause.TryLength;
if (clause.TryOffset <= li.m_offset && li.m_offset < tryEndOffset)
{
// Yes: is it a finally, and is its target outside the try block?
size_t targOffset = (li.m_target - m_methInfo->m_ILCode);
if (clause.Flags == CORINFO_EH_CLAUSE_FINALLY
&& !(clause.TryOffset <= targOffset && targOffset < tryEndOffset))
{
m_ILCodePtr = m_methInfo->m_ILCode + clause.HandlerOffset;
li.m_nextEHIndex = XTnum + 1;
return true;
}
}
}
// Caller will handle popping the leave info stack.
return false;
}
// static
void Interpreter::GCScanRoots(promote_func* pf, ScanContext* sc, void* interp0)
{
Interpreter* interp = reinterpret_cast<Interpreter*>(interp0);
interp->GCScanRoots(pf, sc);
}
void Interpreter::GCScanRoots(promote_func* pf, ScanContext* sc)
{
// Report inbound arguments, if the interpreter has not been invoked directly.
// (In the latter case, the arguments are reported by the calling method.)
if (!m_directCall)
{
for (unsigned i = 0; i < m_methInfo->m_numArgs; i++)
{
GCScanRootAtLoc(reinterpret_cast<Object**>(GetArgAddr(i)), GetArgType(i), pf, sc);
}
}
if (m_methInfo->GetFlag<InterpreterMethodInfo::Flag_hasThisArg>())
{
if (m_methInfo->GetFlag<InterpreterMethodInfo::Flag_thisArgIsObjPtr>())
{
GCScanRootAtLoc(&m_thisArg, InterpreterType(CORINFO_TYPE_CLASS), pf, sc);
}
else
{
GCScanRootAtLoc(&m_thisArg, InterpreterType(CORINFO_TYPE_BYREF), pf, sc);
}
}
// This is the "this" argument passed in to DoCallWork. (Note that we treat this as a byref; it
// might be, for a struct instance method, and this covers the object pointer case as well.)
GCScanRootAtLoc(reinterpret_cast<Object**>(&m_callThisArg), InterpreterType(CORINFO_TYPE_BYREF), pf, sc);
// Scan the exception object that we'll rethrow at the end of the finally block.
GCScanRootAtLoc(reinterpret_cast<Object**>(&m_inFlightException), InterpreterType(CORINFO_TYPE_CLASS), pf, sc);
// A retBufArg, may, in some cases, be a byref into the heap.
if (m_retBufArg != NULL)
{
GCScanRootAtLoc(reinterpret_cast<Object**>(&m_retBufArg), InterpreterType(CORINFO_TYPE_BYREF), pf, sc);
}
if (m_structRetValITPtr != NULL)
{
GCScanRootAtLoc(reinterpret_cast<Object**>(m_structRetValTempSpace), *m_structRetValITPtr, pf, sc);
}
// We'll conservatively assume that we might have a security object.
GCScanRootAtLoc(reinterpret_cast<Object**>(&m_securityObject), InterpreterType(CORINFO_TYPE_CLASS), pf, sc);
// Do locals.
for (unsigned i = 0; i < m_methInfo->m_numLocals; i++)
{
InterpreterType it = m_methInfo->m_localDescs[i].m_type;
void* localPtr = NULL;
if (it.IsLargeStruct(&m_interpCeeInfo))
{
void* structPtr = ArgSlotEndiannessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(i)), sizeof(void**));
localPtr = *reinterpret_cast<void**>(structPtr);
}
else
{
localPtr = ArgSlotEndiannessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(i)), it.Size(&m_interpCeeInfo));
}
GCScanRootAtLoc(reinterpret_cast<Object**>(localPtr), it, pf, sc, m_methInfo->GetPinningBit(i));
}
// Do current ostack.
for (unsigned i = 0; i < m_curStackHt; i++)
{
InterpreterType it = OpStackTypeGet(i);
if (it.IsLargeStruct(&m_interpCeeInfo))
{
Object** structPtr = reinterpret_cast<Object**>(OpStackGet<void*>(i));
// If the ostack value is a pointer to a local var value, don't scan, since we already
// scanned the variable value above.
if (!IsInLargeStructLocalArea(structPtr))
{
GCScanRootAtLoc(structPtr, it, pf, sc);
}
}
else
{
void* stackPtr = OpStackGetAddr(i, it.Size(&m_interpCeeInfo));
GCScanRootAtLoc(reinterpret_cast<Object**>(stackPtr), it, pf, sc);
}
}
// Any outgoing arguments for a call in progress.
for (unsigned i = 0; i < m_argsSize; i++)
{
// If a call has a large struct argument, we'll have pushed a pointer to the entry for that argument on the
// largeStructStack of the current Interpreter. That will be scanned by the code above, so just skip it.
InterpreterType undef(CORINFO_TYPE_UNDEF);
InterpreterType it = m_argTypes[i];
if (it != undef && !it.IsLargeStruct(&m_interpCeeInfo))
{
BYTE* argPtr = ArgSlotEndiannessFixup(&m_args[i], it.Size(&m_interpCeeInfo));
GCScanRootAtLoc(reinterpret_cast<Object**>(argPtr), it, pf, sc);
}
}
}
void Interpreter::GCScanRootAtLoc(Object** loc, InterpreterType it, promote_func* pf, ScanContext* sc, bool pinningRef)
{
switch (it.ToCorInfoType())
{
case CORINFO_TYPE_CLASS:
case CORINFO_TYPE_STRING:
{
DWORD flags = 0;
if (pinningRef) flags |= GC_CALL_PINNED;
(*pf)(loc, sc, flags);
}
break;
case CORINFO_TYPE_BYREF:
case CORINFO_TYPE_REFANY:
{
DWORD flags = GC_CALL_INTERIOR;
if (pinningRef) flags |= GC_CALL_PINNED;
(*pf)(loc, sc, flags);
}
break;
case CORINFO_TYPE_VALUECLASS:
_ASSERTE(!pinningRef);
GCScanValueClassRootAtLoc(loc, it.ToClassHandle(), pf, sc);
break;
default:
_ASSERTE(!pinningRef);
break;
}
}
void Interpreter::GCScanValueClassRootAtLoc(Object** loc, CORINFO_CLASS_HANDLE valueClsHnd, promote_func* pf, ScanContext* sc)
{
MethodTable* valClsMT = GetMethodTableFromClsHnd(valueClsHnd);
ReportPointersFromValueType(pf, sc, valClsMT, loc);
}
// Returns "true" iff "cit" is "stack-normal": all integer types with byte size less than 4
// are folded to CORINFO_TYPE_INT; all remaining unsigned types are folded to their signed counterparts.
bool IsStackNormalType(CorInfoType cit)
{
LIMITED_METHOD_CONTRACT;
switch (cit)
{
case CORINFO_TYPE_UNDEF:
case CORINFO_TYPE_VOID:
case CORINFO_TYPE_BOOL:
case CORINFO_TYPE_CHAR:
case CORINFO_TYPE_BYTE:
case CORINFO_TYPE_UBYTE:
case CORINFO_TYPE_SHORT:
case CORINFO_TYPE_USHORT:
case CORINFO_TYPE_UINT:
case CORINFO_TYPE_NATIVEUINT:
case CORINFO_TYPE_ULONG:
case CORINFO_TYPE_VAR:
case CORINFO_TYPE_STRING:
case CORINFO_TYPE_PTR:
return false;
case CORINFO_TYPE_INT:
case CORINFO_TYPE_NATIVEINT:
case CORINFO_TYPE_BYREF:
case CORINFO_TYPE_CLASS:
case CORINFO_TYPE_LONG:
case CORINFO_TYPE_VALUECLASS:
case CORINFO_TYPE_REFANY:
// I chose to consider both float and double stack-normal; together these comprise
// the "F" type of the ECMA spec. This means I have to consider these to freely
// interconvert.
case CORINFO_TYPE_FLOAT:
case CORINFO_TYPE_DOUBLE:
return true;
default:
UNREACHABLE();
}
}
CorInfoType CorInfoTypeStackNormalize(CorInfoType cit)
{
LIMITED_METHOD_CONTRACT;
switch (cit)
{
case CORINFO_TYPE_UNDEF:
return CORINFO_TYPE_UNDEF;
case CORINFO_TYPE_VOID:
case CORINFO_TYPE_VAR:
_ASSERTE_MSG(false, "Type that cannot be on the ostack.");
return CORINFO_TYPE_UNDEF;
case CORINFO_TYPE_BOOL:
case CORINFO_TYPE_CHAR:
case CORINFO_TYPE_BYTE:
case CORINFO_TYPE_UBYTE:
case CORINFO_TYPE_SHORT:
case CORINFO_TYPE_USHORT:
case CORINFO_TYPE_UINT:
return CORINFO_TYPE_INT;
case CORINFO_TYPE_NATIVEUINT:
case CORINFO_TYPE_PTR:
return CORINFO_TYPE_NATIVEINT;
case CORINFO_TYPE_ULONG:
return CORINFO_TYPE_LONG;
case CORINFO_TYPE_STRING:
return CORINFO_TYPE_CLASS;
case CORINFO_TYPE_INT:
case CORINFO_TYPE_NATIVEINT:
case CORINFO_TYPE_BYREF:
case CORINFO_TYPE_CLASS:
case CORINFO_TYPE_LONG:
case CORINFO_TYPE_VALUECLASS:
case CORINFO_TYPE_REFANY:
// I chose to consider both float and double stack-normal; together these comprise
// the "F" type of the ECMA spec. This means I have to consider these to freely
// interconvert.
case CORINFO_TYPE_FLOAT:
case CORINFO_TYPE_DOUBLE:
_ASSERTE(IsStackNormalType(cit));
return cit;
default:
UNREACHABLE();
}
}
InterpreterType InterpreterType::StackNormalize() const
{
LIMITED_METHOD_CONTRACT;
switch (ToCorInfoType())
{
case CORINFO_TYPE_BOOL:
case CORINFO_TYPE_CHAR:
case CORINFO_TYPE_BYTE:
case CORINFO_TYPE_UBYTE:
case CORINFO_TYPE_SHORT:
case CORINFO_TYPE_USHORT:
case CORINFO_TYPE_UINT:
return InterpreterType(CORINFO_TYPE_INT);
case CORINFO_TYPE_NATIVEUINT:
case CORINFO_TYPE_PTR:
return InterpreterType(CORINFO_TYPE_NATIVEINT);
case CORINFO_TYPE_ULONG:
return InterpreterType(CORINFO_TYPE_LONG);
case CORINFO_TYPE_STRING:
return InterpreterType(CORINFO_TYPE_CLASS);
case CORINFO_TYPE_INT:
case CORINFO_TYPE_NATIVEINT:
case CORINFO_TYPE_BYREF:
case CORINFO_TYPE_CLASS:
case CORINFO_TYPE_LONG:
case CORINFO_TYPE_VALUECLASS:
case CORINFO_TYPE_REFANY:
case CORINFO_TYPE_FLOAT:
case CORINFO_TYPE_DOUBLE:
return *const_cast<InterpreterType*>(this);
case CORINFO_TYPE_UNDEF:
case CORINFO_TYPE_VOID:
case CORINFO_TYPE_VAR:
default:
_ASSERTE_MSG(false, "should not reach here");
return *const_cast<InterpreterType*>(this);
}
}
#ifdef _DEBUG
bool InterpreterType::MatchesWork(const InterpreterType it2, CEEInfo* info) const
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
if (*this == it2) return true;
// Otherwise...
CorInfoType cit1 = ToCorInfoType();
CorInfoType cit2 = it2.ToCorInfoType();
GCX_PREEMP();
// An approximation: valueclasses of the same size match.
if (cit1 == CORINFO_TYPE_VALUECLASS &&
cit2 == CORINFO_TYPE_VALUECLASS &&
Size(info) == it2.Size(info))
{
return true;
}
// NativeInt matches byref. (In unsafe code).
if ((cit1 == CORINFO_TYPE_BYREF && cit2 == CORINFO_TYPE_NATIVEINT))
return true;
// apparently the VM may do the optimization of reporting the return type of a method that
// returns a struct of a single nativeint field *as* nativeint; and similarly with at least some other primitive types.
// So weaken this check to allow that.
// (The check is actually a little weaker still, since I don't want to crack the return type and make sure
// that it has only a single nativeint member -- so I just ensure that the total size is correct).
switch (cit1)
{
case CORINFO_TYPE_NATIVEINT:
case CORINFO_TYPE_NATIVEUINT:
_ASSERTE(sizeof(NativeInt) == sizeof(NativeUInt));
if (it2.Size(info) == sizeof(NativeInt))
return true;
break;
case CORINFO_TYPE_INT:
case CORINFO_TYPE_UINT:
_ASSERTE(sizeof(INT32) == sizeof(UINT32));
if (it2.Size(info) == sizeof(INT32))
return true;
break;
default:
break;
}
// See if the second is a value type synonym for a primitive.
if (cit2 == CORINFO_TYPE_VALUECLASS)
{
CorInfoType cit2prim = info->getTypeForPrimitiveValueClass(it2.ToClassHandle());
if (cit2prim != CORINFO_TYPE_UNDEF)
{
InterpreterType it2prim(cit2prim);
if (*this == it2prim.StackNormalize())
return true;
}
}
// Otherwise...
return false;
}
#endif // _DEBUG
// Static
size_t CorInfoTypeSizeArray[] =
{
/*CORINFO_TYPE_UNDEF = 0x0*/0,
/*CORINFO_TYPE_VOID = 0x1*/0,
/*CORINFO_TYPE_BOOL = 0x2*/1,
/*CORINFO_TYPE_CHAR = 0x3*/2,
/*CORINFO_TYPE_BYTE = 0x4*/1,
/*CORINFO_TYPE_UBYTE = 0x5*/1,
/*CORINFO_TYPE_SHORT = 0x6*/2,
/*CORINFO_TYPE_USHORT = 0x7*/2,
/*CORINFO_TYPE_INT = 0x8*/4,
/*CORINFO_TYPE_UINT = 0x9*/4,
/*CORINFO_TYPE_LONG = 0xa*/8,
/*CORINFO_TYPE_ULONG = 0xb*/8,
/*CORINFO_TYPE_NATIVEINT = 0xc*/sizeof(void*),
/*CORINFO_TYPE_NATIVEUINT = 0xd*/sizeof(void*),
/*CORINFO_TYPE_FLOAT = 0xe*/4,
/*CORINFO_TYPE_DOUBLE = 0xf*/8,
/*CORINFO_TYPE_STRING = 0x10*/sizeof(void*),
/*CORINFO_TYPE_PTR = 0x11*/sizeof(void*),
/*CORINFO_TYPE_BYREF = 0x12*/sizeof(void*),
/*CORINFO_TYPE_VALUECLASS = 0x13*/0,
/*CORINFO_TYPE_CLASS = 0x14*/sizeof(void*),
/*CORINFO_TYPE_REFANY = 0x15*/sizeof(void*)*2,
/*CORINFO_TYPE_VAR = 0x16*/0,
};
bool CorInfoTypeIsUnsigned(CorInfoType cit)
{
LIMITED_METHOD_CONTRACT;
switch (cit)
{
case CORINFO_TYPE_UINT:
case CORINFO_TYPE_NATIVEUINT:
case CORINFO_TYPE_ULONG:
case CORINFO_TYPE_UBYTE:
case CORINFO_TYPE_USHORT:
case CORINFO_TYPE_CHAR:
return true;
default:
return false;
}
}
bool CorInfoTypeIsIntegral(CorInfoType cit)
{
LIMITED_METHOD_CONTRACT;
switch (cit)
{
case CORINFO_TYPE_UINT:
case CORINFO_TYPE_NATIVEUINT:
case CORINFO_TYPE_ULONG:
case CORINFO_TYPE_UBYTE:
case CORINFO_TYPE_USHORT:
case CORINFO_TYPE_INT:
case CORINFO_TYPE_NATIVEINT:
case CORINFO_TYPE_LONG:
case CORINFO_TYPE_BYTE:
case CORINFO_TYPE_BOOL:
case CORINFO_TYPE_SHORT:
return true;
default:
return false;
}
}
bool CorInfoTypeIsFloatingPoint(CorInfoType cit)
{
return cit == CORINFO_TYPE_FLOAT || cit == CORINFO_TYPE_DOUBLE;
}
bool CorInfoTypeIsFloatingPoint(CorInfoHFAElemType cihet)
{
return cihet == CORINFO_HFA_ELEM_FLOAT || cihet == CORINFO_HFA_ELEM_DOUBLE;
}
bool CorElemTypeIsUnsigned(CorElementType cet)
{
LIMITED_METHOD_CONTRACT;
switch (cet)
{
case ELEMENT_TYPE_U1:
case ELEMENT_TYPE_U2:
case ELEMENT_TYPE_U4:
case ELEMENT_TYPE_U8:
case ELEMENT_TYPE_U:
return true;
default:
return false;
}
}
bool CorInfoTypeIsPointer(CorInfoType cit)
{
LIMITED_METHOD_CONTRACT;
switch (cit)
{
case CORINFO_TYPE_PTR:
case CORINFO_TYPE_BYREF:
case CORINFO_TYPE_NATIVEINT:
case CORINFO_TYPE_NATIVEUINT:
return true;
// It seems like the ECMA spec doesn't allow this, but (at least) the managed C++
// compiler expects the explicitly-sized pointer type of the platform pointer size to work:
case CORINFO_TYPE_INT:
case CORINFO_TYPE_UINT:
return sizeof(NativeInt) == sizeof(INT32);
case CORINFO_TYPE_LONG:
case CORINFO_TYPE_ULONG:
return sizeof(NativeInt) == sizeof(INT64);
default:
return false;
}
}
void Interpreter::LdArg(int argNum)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
LdFromMemAddr(GetArgAddr(argNum), GetArgType(argNum));
}
void Interpreter::LdArgA(int argNum)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_BYREF));
OpStackSet<void*>(m_curStackHt, reinterpret_cast<void*>(GetArgAddr(argNum)));
m_curStackHt++;
}
void Interpreter::StArg(int argNum)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
StToLocalMemAddr(GetArgAddr(argNum), GetArgType(argNum));
}
void Interpreter::LdLocA(int locNum)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;
InterpreterType tp = m_methInfo->m_localDescs[locNum].m_type;
void* addr;
if (tp.IsLargeStruct(&m_interpCeeInfo))
{
void* structPtr = ArgSlotEndiannessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(locNum)), sizeof(void**));
addr = *reinterpret_cast<void**>(structPtr);
}
else
{
addr = ArgSlotEndiannessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(locNum)), tp.Size(&m_interpCeeInfo));
}
// The "addr" above, while a byref, is never a heap pointer, so we're robust if
// any of these were to cause a GC.
OpStackSet<void*>(m_curStackHt, addr);
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_BYREF));
m_curStackHt++;
}
void Interpreter::LdIcon(INT32 c)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_INT));
OpStackSet<INT32>(m_curStackHt, c);
m_curStackHt++;
}
void Interpreter::LdR4con(INT32 c)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_FLOAT));
OpStackSet<INT32>(m_curStackHt, c);
m_curStackHt++;
}
void Interpreter::LdLcon(INT64 c)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_LONG));
OpStackSet<INT64>(m_curStackHt, c);
m_curStackHt++;
}
void Interpreter::LdR8con(INT64 c)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_DOUBLE));
OpStackSet<INT64>(m_curStackHt, c);
m_curStackHt++;
}
void Interpreter::LdNull()
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_CLASS));
OpStackSet<void*>(m_curStackHt, NULL);
m_curStackHt++;
}
template<typename T, CorInfoType cit>
void Interpreter::LdInd()
{
_ASSERTE(TOSIsPtr());
_ASSERTE(IsStackNormalType(cit));
unsigned curStackInd = m_curStackHt-1;
T* ptr = OpStackGet<T*>(curStackInd);
ThrowOnInvalidPointer(ptr);
OpStackSet<T>(curStackInd, *ptr);
OpStackTypeSet(curStackInd, InterpreterType(cit));
BarrierIfVolatile();
}
template<typename T, bool isUnsigned>
void Interpreter::LdIndShort()
{
_ASSERTE(TOSIsPtr());
_ASSERTE(sizeof(T) < 4);
unsigned curStackInd = m_curStackHt-1;
T* ptr = OpStackGet<T*>(curStackInd);
ThrowOnInvalidPointer(ptr);
if (isUnsigned)
{
OpStackSet<UINT32>(curStackInd, *ptr);
}
else
{
OpStackSet<INT32>(curStackInd, *ptr);
}
// All short integers are normalized to INT as their stack type.
OpStackTypeSet(curStackInd, InterpreterType(CORINFO_TYPE_INT));
BarrierIfVolatile();
}
template<typename T>
void Interpreter::StInd()
{
_ASSERTE(m_curStackHt >= 2);
_ASSERTE(CorInfoTypeIsPointer(OpStackTypeGet(m_curStackHt-2).ToCorInfoType()));
BarrierIfVolatile();
unsigned stackInd0 = m_curStackHt-2;
unsigned stackInd1 = m_curStackHt-1;
T val = OpStackGet<T>(stackInd1);
T* ptr = OpStackGet<T*>(stackInd0);
ThrowOnInvalidPointer(ptr);
*ptr = val;
m_curStackHt -= 2;
#if INTERP_TRACING
if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL) &&
IsInLocalArea(ptr))
{
PrintLocals();
}
#endif // INTERP_TRACING
}
void Interpreter::StInd_Ref()
{
_ASSERTE(m_curStackHt >= 2);
_ASSERTE(CorInfoTypeIsPointer(OpStackTypeGet(m_curStackHt-2).ToCorInfoType()));
BarrierIfVolatile();
unsigned stackInd0 = m_curStackHt-2;
unsigned stackInd1 = m_curStackHt-1;
OBJECTREF val = ObjectToOBJECTREF(OpStackGet<Object*>(stackInd1));
OBJECTREF* ptr = OpStackGet<OBJECTREF*>(stackInd0);
ThrowOnInvalidPointer(ptr);
SetObjectReference(ptr, val);
m_curStackHt -= 2;
#if INTERP_TRACING
if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL) &&
IsInLocalArea(ptr))
{
PrintLocals();
}
#endif // INTERP_TRACING
}
template<int op>
void Interpreter::BinaryArithOp()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 2);
unsigned op1idx = m_curStackHt - 2;
unsigned op2idx = m_curStackHt - 1;
InterpreterType t1 = OpStackTypeGet(op1idx);
_ASSERTE(IsStackNormalType(t1.ToCorInfoType()));
// Looking at the generated code, it does seem to save some instructions to use the "shifted
// types," though the effect on end-to-end time is variable. So I'll leave it set.
InterpreterType t2 = OpStackTypeGet(op2idx);
_ASSERTE(IsStackNormalType(t2.ToCorInfoType()));
// In all cases belows, since "op" is compile-time constant, "if" chains on it should fold away.
switch (t1.ToCorInfoTypeShifted())
{
case CORINFO_TYPE_SHIFTED_INT:
if (t1 == t2)
{
// Int op Int = Int
INT32 val1 = OpStackGet<INT32>(op1idx);
INT32 val2 = OpStackGet<INT32>(op2idx);
BinaryArithOpWork<op, INT32, /*IsIntType*/true, CORINFO_TYPE_INT, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
CorInfoTypeShifted cits2 = t2.ToCorInfoTypeShifted();
if (cits2 == CORINFO_TYPE_SHIFTED_NATIVEINT)
{
// Int op NativeInt = NativeInt
NativeInt val1 = static_cast<NativeInt>(OpStackGet<INT32>(op1idx));
NativeInt val2 = OpStackGet<NativeInt>(op2idx);
BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/false>(val1, val2);
}
else if (s_InterpreterLooseRules && cits2 == CORINFO_TYPE_SHIFTED_LONG)
{
// Int op Long = Long
INT64 val1 = static_cast<INT64>(OpStackGet<INT32>(op1idx));
INT64 val2 = OpStackGet<INT64>(op2idx);
BinaryArithOpWork<op, INT64, /*IsIntType*/true, CORINFO_TYPE_LONG, /*TypeIsUnchanged*/false>(val1, val2);
}
else if (cits2 == CORINFO_TYPE_SHIFTED_BYREF)
{
if (op == BA_Add || (s_InterpreterLooseRules && op == BA_Sub))
{
// Int + ByRef = ByRef
NativeInt val1 = static_cast<NativeInt>(OpStackGet<INT32>(op1idx));
NativeInt val2 = OpStackGet<NativeInt>(op2idx);
BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/false>(val1, val2);
}
else
{
VerificationError("Operation not permitted on int and managed pointer.");
}
}
else
{
VerificationError("Binary arithmetic operation type mismatch (int and ?)");
}
}
break;
case CORINFO_TYPE_SHIFTED_NATIVEINT:
{
NativeInt val1 = OpStackGet<NativeInt>(op1idx);
if (t1 == t2)
{
// NativeInt op NativeInt = NativeInt
NativeInt val2 = OpStackGet<NativeInt>(op2idx);
BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
CorInfoTypeShifted cits2 = t2.ToCorInfoTypeShifted();
if (cits2 == CORINFO_TYPE_SHIFTED_INT)
{
// NativeInt op Int = NativeInt
NativeInt val2 = static_cast<NativeInt>(OpStackGet<INT32>(op2idx));
BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
}
// CLI spec does not allow adding a native int and an int64. So use loose rules.
else if (s_InterpreterLooseRules && cits2 == CORINFO_TYPE_SHIFTED_LONG)
{
// NativeInt op Int = NativeInt
NativeInt val2 = static_cast<NativeInt>(OpStackGet<INT64>(op2idx));
BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
}
else if (cits2 == CORINFO_TYPE_SHIFTED_BYREF)
{
if (op == BA_Add || (s_InterpreterLooseRules && op == BA_Sub))
{
// NativeInt + ByRef = ByRef
NativeInt val2 = OpStackGet<NativeInt>(op2idx);
BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/false>(val1, val2);
}
else
{
VerificationError("Operation not permitted on native int and managed pointer.");
}
}
else
{
VerificationError("Binary arithmetic operation type mismatch (native int and ?)");
}
}
}
break;
case CORINFO_TYPE_SHIFTED_LONG:
{
bool looseLong = false;
#if defined(HOST_AMD64)
looseLong = (s_InterpreterLooseRules && (t2.ToCorInfoType() == CORINFO_TYPE_NATIVEINT ||
t2.ToCorInfoType() == CORINFO_TYPE_BYREF));
#endif
if (t1 == t2 || looseLong)
{
// Long op Long = Long
INT64 val1 = OpStackGet<INT64>(op1idx);
INT64 val2 = OpStackGet<INT64>(op2idx);
BinaryArithOpWork<op, INT64, /*IsIntType*/true, CORINFO_TYPE_LONG, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
VerificationError("Binary arithmetic operation type mismatch (long and ?)");
}
}
break;
case CORINFO_TYPE_SHIFTED_FLOAT:
{
if (t1 == t2)
{
// Float op Float = Float
float val1 = OpStackGet<float>(op1idx);
float val2 = OpStackGet<float>(op2idx);
BinaryArithOpWork<op, float, /*IsIntType*/false, CORINFO_TYPE_FLOAT, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
CorInfoTypeShifted cits2 = t2.ToCorInfoTypeShifted();
if (cits2 == CORINFO_TYPE_SHIFTED_DOUBLE)
{
// Float op Double = Double
double val1 = static_cast<double>(OpStackGet<float>(op1idx));
double val2 = OpStackGet<double>(op2idx);
BinaryArithOpWork<op, double, /*IsIntType*/false, CORINFO_TYPE_DOUBLE, /*TypeIsUnchanged*/false>(val1, val2);
}
else
{
VerificationError("Binary arithmetic operation type mismatch (float and ?)");
}
}
}
break;
case CORINFO_TYPE_SHIFTED_DOUBLE:
{
if (t1 == t2)
{
// Double op Double = Double
double val1 = OpStackGet<double>(op1idx);
double val2 = OpStackGet<double>(op2idx);
BinaryArithOpWork<op, double, /*IsIntType*/false, CORINFO_TYPE_DOUBLE, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
CorInfoTypeShifted cits2 = t2.ToCorInfoTypeShifted();
if (cits2 == CORINFO_TYPE_SHIFTED_FLOAT)
{
// Double op Float = Double
double val1 = OpStackGet<double>(op1idx);
double val2 = static_cast<double>(OpStackGet<float>(op2idx));
BinaryArithOpWork<op, double, /*IsIntType*/false, CORINFO_TYPE_DOUBLE, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
VerificationError("Binary arithmetic operation type mismatch (double and ?)");
}
}
}
break;
case CORINFO_TYPE_SHIFTED_BYREF:
{
NativeInt val1 = OpStackGet<NativeInt>(op1idx);
CorInfoTypeShifted cits2 = t2.ToCorInfoTypeShifted();
if (cits2 == CORINFO_TYPE_SHIFTED_INT)
{
if (op == BA_Add || op == BA_Sub)
{
// ByRef +- Int = ByRef
NativeInt val2 = static_cast<NativeInt>(OpStackGet<INT32>(op2idx));
BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
VerificationError("May only add/subtract managed pointer and integral value.");
}
}
else if (cits2 == CORINFO_TYPE_SHIFTED_NATIVEINT)
{
if (op == BA_Add || op == BA_Sub)
{
// ByRef +- NativeInt = ByRef
NativeInt val2 = OpStackGet<NativeInt>(op2idx);
BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
VerificationError("May only add/subtract managed pointer and integral value.");
}
}
else if (cits2 == CORINFO_TYPE_SHIFTED_BYREF)
{
if (op == BA_Sub)
{
// ByRef - ByRef = NativeInt
NativeInt val2 = OpStackGet<NativeInt>(op2idx);
BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/false>(val1, val2);
}
else
{
VerificationError("May only subtract managed pointer values.");
}
}
// CLI spec does not allow adding a native int and an int64. So use loose rules.
else if (s_InterpreterLooseRules && cits2 == CORINFO_TYPE_SHIFTED_LONG)
{
// NativeInt op Int = NativeInt
NativeInt val2 = static_cast<NativeInt>(OpStackGet<INT64>(op2idx));
BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
VerificationError("Binary arithmetic operation not permitted on byref");
}
}
break;
case CORINFO_TYPE_SHIFTED_CLASS:
VerificationError("Can't do binary arithmetic on object references.");
break;
default:
_ASSERTE_MSG(false, "Non-stack-normal type on stack.");
}
// In all cases:
m_curStackHt--;
}
template<int op, bool asUnsigned>
void Interpreter::BinaryArithOvfOp()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 2);
unsigned op1idx = m_curStackHt - 2;
unsigned op2idx = m_curStackHt - 1;
InterpreterType t1 = OpStackTypeGet(op1idx);
CorInfoType cit1 = t1.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit1));
InterpreterType t2 = OpStackTypeGet(op2idx);
CorInfoType cit2 = t2.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit2));
// In all cases belows, since "op" is compile-time constant, "if" chains on it should fold away.
switch (cit1)
{
case CORINFO_TYPE_INT:
if (cit2 == CORINFO_TYPE_INT)
{
if (asUnsigned)
{
// UnsignedInt op UnsignedInt = UnsignedInt
UINT32 val1 = OpStackGet<UINT32>(op1idx);
UINT32 val2 = OpStackGet<UINT32>(op2idx);
BinaryArithOvfOpWork<op, UINT32, CORINFO_TYPE_INT, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
// Int op Int = Int
INT32 val1 = OpStackGet<INT32>(op1idx);
INT32 val2 = OpStackGet<INT32>(op2idx);
BinaryArithOvfOpWork<op, INT32, CORINFO_TYPE_INT, /*TypeIsUnchanged*/true>(val1, val2);
}
}
else if (cit2 == CORINFO_TYPE_NATIVEINT)
{
if (asUnsigned)
{
// UnsignedInt op UnsignedNativeInt = UnsignedNativeInt
NativeUInt val1 = static_cast<NativeUInt>(OpStackGet<UINT32>(op1idx));
NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/false>(val1, val2);
}
else
{
// Int op NativeInt = NativeInt
NativeInt val1 = static_cast<NativeInt>(OpStackGet<INT32>(op1idx));
NativeInt val2 = OpStackGet<NativeInt>(op2idx);
BinaryArithOvfOpWork<op, NativeInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/false>(val1, val2);
}
}
else if (cit2 == CORINFO_TYPE_BYREF)
{
if (asUnsigned && op == BA_Add)
{
// UnsignedInt + ByRef = ByRef
NativeUInt val1 = static_cast<NativeUInt>(OpStackGet<UINT32>(op1idx));
NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/false>(val1, val2);
}
else
{
VerificationError("Illegal arithmetic overflow operation for int and byref.");
}
}
else
{
VerificationError("Binary arithmetic overflow operation type mismatch (int and ?)");
}
break;
case CORINFO_TYPE_NATIVEINT:
if (cit2 == CORINFO_TYPE_INT)
{
if (asUnsigned)
{
// UnsignedNativeInt op UnsignedInt = UnsignedNativeInt
NativeUInt val1 = OpStackGet<NativeUInt>(op1idx);
NativeUInt val2 = static_cast<NativeUInt>(OpStackGet<UINT32>(op2idx));
BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
// NativeInt op Int = NativeInt
NativeInt val1 = OpStackGet<NativeInt>(op1idx);
NativeInt val2 = static_cast<NativeInt>(OpStackGet<INT32>(op2idx));
BinaryArithOvfOpWork<op, NativeInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
}
}
else if (cit2 == CORINFO_TYPE_NATIVEINT)
{
if (asUnsigned)
{
// UnsignedNativeInt op UnsignedNativeInt = UnsignedNativeInt
NativeUInt val1 = OpStackGet<NativeUInt>(op1idx);
NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
// NativeInt op NativeInt = NativeInt
NativeInt val1 = OpStackGet<NativeInt>(op1idx);
NativeInt val2 = OpStackGet<NativeInt>(op2idx);
BinaryArithOvfOpWork<op, NativeInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
}
}
else if (cit2 == CORINFO_TYPE_BYREF)
{
if (asUnsigned && op == BA_Add)
{
// UnsignedNativeInt op ByRef = ByRef
NativeUInt val1 = OpStackGet<UINT32>(op1idx);
NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/false>(val1, val2);
}
else
{
VerificationError("Illegal arithmetic overflow operation for native int and byref.");
}
}
else
{
VerificationError("Binary arithmetic overflow operation type mismatch (native int and ?)");
}
break;
case CORINFO_TYPE_LONG:
if (cit2 == CORINFO_TYPE_LONG || (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_NATIVEINT))
{
if (asUnsigned)
{
// UnsignedLong op UnsignedLong = UnsignedLong
UINT64 val1 = OpStackGet<UINT64>(op1idx);
UINT64 val2 = OpStackGet<UINT64>(op2idx);
BinaryArithOvfOpWork<op, UINT64, CORINFO_TYPE_LONG, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
// Long op Long = Long
INT64 val1 = OpStackGet<INT64>(op1idx);
INT64 val2 = OpStackGet<INT64>(op2idx);
BinaryArithOvfOpWork<op, INT64, CORINFO_TYPE_LONG, /*TypeIsUnchanged*/true>(val1, val2);
}
}
else
{
VerificationError("Binary arithmetic overflow operation type mismatch (long and ?)");
}
break;
case CORINFO_TYPE_BYREF:
if (asUnsigned && (op == BA_Add || op == BA_Sub))
{
NativeUInt val1 = OpStackGet<NativeUInt>(op1idx);
if (cit2 == CORINFO_TYPE_INT)
{
// ByRef +- UnsignedInt = ByRef
NativeUInt val2 = static_cast<NativeUInt>(OpStackGet<INT32>(op2idx));
BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/true>(val1, val2);
}
else if (cit2 == CORINFO_TYPE_NATIVEINT)
{
// ByRef +- UnsignedNativeInt = ByRef
NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/true>(val1, val2);
}
else if (cit2 == CORINFO_TYPE_BYREF)
{
if (op == BA_Sub)
{
// ByRef - ByRef = UnsignedNativeInt
NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/false>(val1, val2);
}
else
{
VerificationError("Illegal arithmetic overflow operation for byref and byref: may only subtract managed pointer values.");
}
}
else
{
VerificationError("Binary arithmetic overflow operation not permitted on byref");
}
}
else
{
if (!asUnsigned)
{
VerificationError("Signed binary arithmetic overflow operation not permitted on managed pointer values.");
}
else
{
_ASSERTE_MSG(op == BA_Mul, "Must be an overflow operation; tested for Add || Sub above.");
VerificationError("Cannot multiply managed pointer values.");
}
}
break;
default:
_ASSERTE_MSG(false, "Non-stack-normal type on stack.");
}
// In all cases:
m_curStackHt--;
}
template<int op, typename T, CorInfoType cit, bool TypeIsUnchanged>
void Interpreter::BinaryArithOvfOpWork(T val1, T val2)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
ClrSafeInt<T> res;
ClrSafeInt<T> safeV1(val1);
ClrSafeInt<T> safeV2(val2);
if (op == BA_Add)
{
res = safeV1 + safeV2;
}
else if (op == BA_Sub)
{
res = safeV1 - safeV2;
}
else if (op == BA_Mul)
{
res = safeV1 * safeV2;
}
else
{
_ASSERTE_MSG(false, "op should be one of the overflow ops...");
}
if (res.IsOverflow())
{
ThrowOverflowException();
}
unsigned residx = m_curStackHt - 2;
OpStackSet<T>(residx, res.Value());
if (!TypeIsUnchanged)
{
OpStackTypeSet(residx, InterpreterType(cit));
}
}
template<int op>
void Interpreter::BinaryIntOp()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 2);
unsigned op1idx = m_curStackHt - 2;
unsigned op2idx = m_curStackHt - 1;
InterpreterType t1 = OpStackTypeGet(op1idx);
CorInfoType cit1 = t1.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit1));
InterpreterType t2 = OpStackTypeGet(op2idx);
CorInfoType cit2 = t2.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit2));
// In all cases belows, since "op" is compile-time constant, "if" chains on it should fold away.
switch (cit1)
{
case CORINFO_TYPE_INT:
if (cit2 == CORINFO_TYPE_INT)
{
// Int op Int = Int
UINT32 val1 = OpStackGet<UINT32>(op1idx);
UINT32 val2 = OpStackGet<UINT32>(op2idx);
BinaryIntOpWork<op, UINT32, CORINFO_TYPE_INT, /*TypeIsUnchanged*/true>(val1, val2);
}
else if (cit2 == CORINFO_TYPE_NATIVEINT)
{
// Int op NativeInt = NativeInt
NativeUInt val1 = static_cast<NativeUInt>(OpStackGet<INT32>(op1idx));
NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
BinaryIntOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/false>(val1, val2);
}
else if (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_BYREF)
{
// Int op NativeUInt = NativeUInt
NativeUInt val1 = static_cast<NativeUInt>(OpStackGet<INT32>(op1idx));
NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
BinaryIntOpWork<op, NativeUInt, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/false>(val1, val2);
}
else
{
VerificationError("Binary arithmetic operation type mismatch (int and ?)");
}
break;
case CORINFO_TYPE_NATIVEINT:
if (cit2 == CORINFO_TYPE_NATIVEINT)
{
// NativeInt op NativeInt = NativeInt
NativeUInt val1 = OpStackGet<NativeUInt>(op1idx);
NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
BinaryIntOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
}
else if (cit2 == CORINFO_TYPE_INT)
{
// NativeInt op Int = NativeInt
NativeUInt val1 = OpStackGet<NativeUInt>(op1idx);
NativeUInt val2 = static_cast<NativeUInt>(OpStackGet<INT32>(op2idx));
BinaryIntOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
}
// CLI spec does not allow adding a native int and an int64. So use loose rules.
else if (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_LONG)
{
// NativeInt op Int = NativeInt
NativeUInt val1 = OpStackGet<NativeUInt>(op1idx);
NativeUInt val2 = static_cast<NativeUInt>(OpStackGet<INT64>(op2idx));
BinaryIntOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
VerificationError("Binary arithmetic operation type mismatch (native int and ?)");
}
break;
case CORINFO_TYPE_LONG:
if (cit2 == CORINFO_TYPE_LONG || (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_NATIVEINT))
{
// Long op Long = Long
UINT64 val1 = OpStackGet<UINT64>(op1idx);
UINT64 val2 = OpStackGet<UINT64>(op2idx);
BinaryIntOpWork<op, UINT64, CORINFO_TYPE_LONG, /*TypeIsUnchanged*/true>(val1, val2);
}
else
{
VerificationError("Binary arithmetic operation type mismatch (long and ?)");
}
break;
default:
VerificationError("Illegal operation for non-integral data type.");
}
// In all cases:
m_curStackHt--;
}
template<int op, typename T, CorInfoType cit, bool TypeIsUnchanged>
void Interpreter::BinaryIntOpWork(T val1, T val2)
{
T res;
if (op == BIO_And)
{
res = val1 & val2;
}
else if (op == BIO_Or)
{
res = val1 | val2;
}
else if (op == BIO_Xor)
{
res = val1 ^ val2;
}
else
{
_ASSERTE(op == BIO_DivUn || op == BIO_RemUn);
if (val2 == 0)
{
ThrowDivideByZero();
}
else if (val2 == static_cast<T>(-1) && val1 == static_cast<T>(((UINT64)1) << (sizeof(T)*8 - 1))) // min int / -1 is not representable.
{
ThrowSysArithException();
}
// Otherwise...
if (op == BIO_DivUn)
{
res = val1 / val2;
}
else
{
res = val1 % val2;
}
}
unsigned residx = m_curStackHt - 2;
OpStackSet<T>(residx, res);
if (!TypeIsUnchanged)
{
OpStackTypeSet(residx, InterpreterType(cit));
}
}
template<int op>
void Interpreter::ShiftOp()
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 2);
unsigned op1idx = m_curStackHt - 2;
unsigned op2idx = m_curStackHt - 1;
InterpreterType t1 = OpStackTypeGet(op1idx);
CorInfoType cit1 = t1.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit1));
InterpreterType t2 = OpStackTypeGet(op2idx);
CorInfoType cit2 = t2.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit2));
// In all cases belows, since "op" is compile-time constant, "if" chains on it should fold away.
switch (cit1)
{
case CORINFO_TYPE_INT:
ShiftOpWork<op, INT32, UINT32>(op1idx, cit2);
break;
case CORINFO_TYPE_NATIVEINT:
ShiftOpWork<op, NativeInt, NativeUInt>(op1idx, cit2);
break;
case CORINFO_TYPE_LONG:
ShiftOpWork<op, INT64, UINT64>(op1idx, cit2);
break;
default:
VerificationError("Illegal value type for shift operation.");
break;
}
m_curStackHt--;
}
template<int op, typename T, typename UT>
void Interpreter::ShiftOpWork(unsigned op1idx, CorInfoType cit2)
{
T val = OpStackGet<T>(op1idx);
unsigned op2idx = op1idx + 1;
T res = 0;
if (cit2 == CORINFO_TYPE_INT)
{
INT32 shiftAmt = OpStackGet<INT32>(op2idx);
if (op == CEE_SHL)
{
res = val << shiftAmt; // TODO: Check that C++ semantics matches IL.
}
else if (op == CEE_SHR)
{
res = val >> shiftAmt;
}
else
{
_ASSERTE(op == CEE_SHR_UN);
res = (static_cast<UT>(val)) >> shiftAmt;
}
}
else if (cit2 == CORINFO_TYPE_NATIVEINT)
{
NativeInt shiftAmt = OpStackGet<NativeInt>(op2idx);
if (op == CEE_SHL)
{
res = val << shiftAmt; // TODO: Check that C++ semantics matches IL.
}
else if (op == CEE_SHR)
{
res = val >> shiftAmt;
}
else
{
_ASSERTE(op == CEE_SHR_UN);
res = (static_cast<UT>(val)) >> shiftAmt;
}
}
else
{
VerificationError("Operand type mismatch for shift operator.");
}
OpStackSet<T>(op1idx, res);
}
void Interpreter::Neg()
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 1);
unsigned opidx = m_curStackHt - 1;
InterpreterType t1 = OpStackTypeGet(opidx);
CorInfoType cit1 = t1.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit1));
switch (cit1)
{
case CORINFO_TYPE_INT:
OpStackSet<INT32>(opidx, -OpStackGet<INT32>(opidx));
break;
case CORINFO_TYPE_NATIVEINT:
OpStackSet<NativeInt>(opidx, -OpStackGet<NativeInt>(opidx));
break;
case CORINFO_TYPE_LONG:
OpStackSet<INT64>(opidx, -OpStackGet<INT64>(opidx));
break;
case CORINFO_TYPE_FLOAT:
OpStackSet<float>(opidx, -OpStackGet<float>(opidx));
break;
case CORINFO_TYPE_DOUBLE:
OpStackSet<double>(opidx, -OpStackGet<double>(opidx));
break;
default:
VerificationError("Illegal operand type for Neg operation.");
}
}
void Interpreter::Not()
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 1);
unsigned opidx = m_curStackHt - 1;
InterpreterType t1 = OpStackTypeGet(opidx);
CorInfoType cit1 = t1.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit1));
switch (cit1)
{
case CORINFO_TYPE_INT:
OpStackSet<INT32>(opidx, ~OpStackGet<INT32>(opidx));
break;
case CORINFO_TYPE_NATIVEINT:
OpStackSet<NativeInt>(opidx, ~OpStackGet<NativeInt>(opidx));
break;
case CORINFO_TYPE_LONG:
OpStackSet<INT64>(opidx, ~OpStackGet<INT64>(opidx));
break;
default:
VerificationError("Illegal operand type for Not operation.");
}
}
template<typename T, bool TIsUnsigned, bool TCanHoldPtr, bool TIsShort, CorInfoType cit>
void Interpreter::Conv()
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 1);
unsigned opidx = m_curStackHt - 1;
InterpreterType t1 = OpStackTypeGet(opidx);
CorInfoType cit1 = t1.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit1));
T val;
switch (cit1)
{
case CORINFO_TYPE_INT:
if (TIsUnsigned)
{
// Must convert the 32 bit value to unsigned first, so that we zero-extend if necessary.
val = static_cast<T>(static_cast<UINT32>(OpStackGet<INT32>(opidx)));
}
else
{
val = static_cast<T>(OpStackGet<INT32>(opidx));
}
break;
case CORINFO_TYPE_NATIVEINT:
if (TIsUnsigned)
{
// NativeInt might be 32 bits, so convert to unsigned before possibly widening.
val = static_cast<T>(static_cast<NativeUInt>(OpStackGet<NativeInt>(opidx)));
}
else
{
val = static_cast<T>(OpStackGet<NativeInt>(opidx));
}
break;
case CORINFO_TYPE_LONG:
val = static_cast<T>(OpStackGet<INT64>(opidx));
break;
// TODO: Make sure that the C++ conversions do the right thing (truncate to zero...)
case CORINFO_TYPE_FLOAT:
val = static_cast<T>(OpStackGet<float>(opidx));
break;
case CORINFO_TYPE_DOUBLE:
val = static_cast<T>(OpStackGet<double>(opidx));
break;
case CORINFO_TYPE_BYREF:
case CORINFO_TYPE_CLASS:
case CORINFO_TYPE_STRING:
if (!TCanHoldPtr && !s_InterpreterLooseRules)
{
VerificationError("Conversion of pointer value to type that can't hold its value.");
}
// Otherwise...
// (Must first convert to NativeInt, because the compiler believes this might be applied for T =
// float or double. It won't, by the test above, and the extra cast shouldn't generate any code...)
val = static_cast<T>(reinterpret_cast<NativeInt>(OpStackGet<void*>(opidx)));
break;
default:
VerificationError("Illegal operand type for conv.* operation.");
UNREACHABLE();
}
if (TIsShort)
{
OpStackSet<INT32>(opidx, static_cast<INT32>(val));
}
else
{
OpStackSet<T>(opidx, val);
}
OpStackTypeSet(opidx, InterpreterType(cit));
}
void Interpreter::ConvRUn()
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 1);
unsigned opidx = m_curStackHt - 1;
InterpreterType t1 = OpStackTypeGet(opidx);
CorInfoType cit1 = t1.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit1));
switch (cit1)
{
case CORINFO_TYPE_INT:
OpStackSet<double>(opidx, static_cast<double>(OpStackGet<UINT32>(opidx)));
break;
case CORINFO_TYPE_NATIVEINT:
OpStackSet<double>(opidx, static_cast<double>(OpStackGet<NativeUInt>(opidx)));
break;
case CORINFO_TYPE_LONG:
OpStackSet<double>(opidx, static_cast<double>(OpStackGet<UINT64>(opidx)));
break;
case CORINFO_TYPE_DOUBLE:
return;
default:
VerificationError("Illegal operand type for conv.r.un operation.");
}
OpStackTypeSet(opidx, InterpreterType(CORINFO_TYPE_DOUBLE));
}
template<typename T, INT64 TMin, UINT64 TMax, bool TCanHoldPtr, CorInfoType cit>
void Interpreter::ConvOvf()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 1);
unsigned opidx = m_curStackHt - 1;
InterpreterType t1 = OpStackTypeGet(opidx);
CorInfoType cit1 = t1.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit1));
switch (cit1)
{
case CORINFO_TYPE_INT:
{
INT32 i4 = OpStackGet<INT32>(opidx);
if (!FitsIn<T>(i4))
{
ThrowOverflowException();
}
OpStackSet<T>(opidx, static_cast<T>(i4));
}
break;
case CORINFO_TYPE_NATIVEINT:
{
NativeInt i = OpStackGet<NativeInt>(opidx);
if (!FitsIn<T>(i))
{
ThrowOverflowException();
}
OpStackSet<T>(opidx, static_cast<T>(i));
}
break;
case CORINFO_TYPE_LONG:
{
INT64 i8 = OpStackGet<INT64>(opidx);
if (!FitsIn<T>(i8))
{
ThrowOverflowException();
}
OpStackSet<T>(opidx, static_cast<T>(i8));
}
break;
// Make sure that the C++ conversions do the right thing (truncate to zero...)
case CORINFO_TYPE_FLOAT:
{
float f = OpStackGet<float>(opidx);
if (!FloatFitsInIntType<TMin, TMax>(f))
{
ThrowOverflowException();
}
OpStackSet<T>(opidx, static_cast<T>(f));
}
break;
case CORINFO_TYPE_DOUBLE:
{
double d = OpStackGet<double>(opidx);
if (!DoubleFitsInIntType<TMin, TMax>(d))
{
ThrowOverflowException();
}
OpStackSet<T>(opidx, static_cast<T>(d));
}
break;
case CORINFO_TYPE_BYREF:
case CORINFO_TYPE_CLASS:
case CORINFO_TYPE_STRING:
if (!TCanHoldPtr)
{
VerificationError("Conversion of pointer value to type that can't hold its value.");
}
// Otherwise...
// (Must first convert to NativeInt, because the compiler believes this might be applied for T =
// float or double. It won't, by the test above, and the extra cast shouldn't generate any code...
OpStackSet<T>(opidx, static_cast<T>(reinterpret_cast<NativeInt>(OpStackGet<void*>(opidx))));
break;
default:
VerificationError("Illegal operand type for conv.ovf.* operation.");
}
_ASSERTE_MSG(IsStackNormalType(cit), "Precondition.");
OpStackTypeSet(opidx, InterpreterType(cit));
}
template<typename T, INT64 TMin, UINT64 TMax, bool TCanHoldPtr, CorInfoType cit>
void Interpreter::ConvOvfUn()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 1);
unsigned opidx = m_curStackHt - 1;
InterpreterType t1 = OpStackTypeGet(opidx);
CorInfoType cit1 = t1.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit1));
switch (cit1)
{
case CORINFO_TYPE_INT:
{
UINT32 ui4 = OpStackGet<UINT32>(opidx);
if (!FitsIn<T>(ui4))
{
ThrowOverflowException();
}
OpStackSet<T>(opidx, static_cast<T>(ui4));
}
break;
case CORINFO_TYPE_NATIVEINT:
{
NativeUInt ui = OpStackGet<NativeUInt>(opidx);
if (!FitsIn<T>(ui))
{
ThrowOverflowException();
}
OpStackSet<T>(opidx, static_cast<T>(ui));
}
break;
case CORINFO_TYPE_LONG:
{
UINT64 ui8 = OpStackGet<UINT64>(opidx);
if (!FitsIn<T>(ui8))
{
ThrowOverflowException();
}
OpStackSet<T>(opidx, static_cast<T>(ui8));
}
break;
// Make sure that the C++ conversions do the right thing (truncate to zero...)
case CORINFO_TYPE_FLOAT:
{
float f = OpStackGet<float>(opidx);
if (!FloatFitsInIntType<TMin, TMax>(f))
{
ThrowOverflowException();
}
OpStackSet<T>(opidx, static_cast<T>(f));
}
break;
case CORINFO_TYPE_DOUBLE:
{
double d = OpStackGet<double>(opidx);
if (!DoubleFitsInIntType<TMin, TMax>(d))
{
ThrowOverflowException();
}
OpStackSet<T>(opidx, static_cast<T>(d));
}
break;
case CORINFO_TYPE_BYREF:
case CORINFO_TYPE_CLASS:
case CORINFO_TYPE_STRING:
if (!TCanHoldPtr)
{
VerificationError("Conversion of pointer value to type that can't hold its value.");
}
// Otherwise...
// (Must first convert to NativeInt, because the compiler believes this might be applied for T =
// float or double. It won't, by the test above, and the extra cast shouldn't generate any code...
OpStackSet<T>(opidx, static_cast<T>(reinterpret_cast<NativeInt>(OpStackGet<void*>(opidx))));
break;
default:
VerificationError("Illegal operand type for conv.ovf.*.un operation.");
}
_ASSERTE_MSG(IsStackNormalType(cit), "Precondition.");
OpStackTypeSet(opidx, InterpreterType(cit));
}
void Interpreter::LdObj()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
BarrierIfVolatile();
_ASSERTE(m_curStackHt > 0);
unsigned ind = m_curStackHt - 1;
#ifdef _DEBUG
CorInfoType cit = OpStackTypeGet(ind).ToCorInfoType();
_ASSERTE_MSG(IsValidPointerType(cit), "Expect pointer on stack");
#endif // _DEBUG
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdObj]);
#endif // INTERP_TRACING
// TODO: GetTypeFromToken also uses GCX_PREEMP(); can we merge it with the getClassAttribs() block below, and do it just once?
CORINFO_CLASS_HANDLE clsHnd = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_LdObj));
DWORD clsAttribs;
{
GCX_PREEMP();
clsAttribs = m_interpCeeInfo.getClassAttribs(clsHnd);
}
void* src = OpStackGet<void*>(ind);
ThrowOnInvalidPointer(src);
if (clsAttribs & CORINFO_FLG_VALUECLASS)
{
LdObjValueClassWork(clsHnd, ind, src);
}
else
{
OpStackSet<void*>(ind, *reinterpret_cast<void**>(src));
OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_CLASS));
}
m_ILCodePtr += 5;
}
void Interpreter::LdObjValueClassWork(CORINFO_CLASS_HANDLE valueClsHnd, unsigned ind, void* src)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
// "src" is a byref, which may be into an object. GCPROTECT for the call below.
GCPROTECT_BEGININTERIOR(src);
InterpreterType it = InterpreterType(&m_interpCeeInfo, valueClsHnd);
size_t sz = it.Size(&m_interpCeeInfo);
// Note that the memcpy's below are permissible because the destination is in the operand stack.
if (sz > sizeof(INT64))
{
void* dest = LargeStructOperandStackPush(sz);
memcpy(dest, src, sz);
OpStackSet<void*>(ind, dest);
}
else
{
OpStackSet<INT64>(ind, GetSmallStructValue(src, sz));
}
OpStackTypeSet(ind, it.StackNormalize());
GCPROTECT_END();
}
CORINFO_CLASS_HANDLE Interpreter::GetTypeFromToken(BYTE* codePtr, CorInfoTokenKind tokKind InterpTracingArg(ResolveTokenKind rtk))
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
GCX_PREEMP();
CORINFO_RESOLVED_TOKEN typeTok;
ResolveToken(&typeTok, getU4LittleEndian(codePtr), tokKind InterpTracingArg(rtk));
return typeTok.hClass;
}
bool Interpreter::IsValidPointerType(CorInfoType cit)
{
bool isValid = (cit == CORINFO_TYPE_NATIVEINT || cit == CORINFO_TYPE_BYREF);
#if defined(HOST_AMD64)
isValid = isValid || (s_InterpreterLooseRules && cit == CORINFO_TYPE_LONG);
#endif
return isValid;
}
void Interpreter::CpObj()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 2);
unsigned destInd = m_curStackHt - 2;
unsigned srcInd = m_curStackHt - 1;
#ifdef _DEBUG
// Check that src and dest are both pointer types.
CorInfoType cit = OpStackTypeGet(destInd).ToCorInfoType();
_ASSERTE_MSG(IsValidPointerType(cit), "Expect pointer on stack for dest of cpobj");
cit = OpStackTypeGet(srcInd).ToCorInfoType();
_ASSERTE_MSG(IsValidPointerType(cit), "Expect pointer on stack for src of cpobj");
#endif // _DEBUG
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_CpObj]);
#endif // INTERP_TRACING
CORINFO_CLASS_HANDLE clsHnd = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_CpObj));
DWORD clsAttribs;
{
GCX_PREEMP();
clsAttribs = m_interpCeeInfo.getClassAttribs(clsHnd);
}
void* dest = OpStackGet<void*>(destInd);
void* src = OpStackGet<void*>(srcInd);
ThrowOnInvalidPointer(dest);
ThrowOnInvalidPointer(src);
// dest and src are vulnerable byrefs.
GCX_FORBID();
if (clsAttribs & CORINFO_FLG_VALUECLASS)
{
CopyValueClassUnchecked(dest, src, GetMethodTableFromClsHnd(clsHnd));
}
else
{
OBJECTREF val = *reinterpret_cast<OBJECTREF*>(src);
SetObjectReference(reinterpret_cast<OBJECTREF*>(dest), val);
}
m_curStackHt -= 2;
m_ILCodePtr += 5;
}
void Interpreter::StObj()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 2);
unsigned destInd = m_curStackHt - 2;
unsigned valInd = m_curStackHt - 1;
#ifdef _DEBUG
// Check that dest is a pointer type.
CorInfoType cit = OpStackTypeGet(destInd).ToCorInfoType();
_ASSERTE_MSG(IsValidPointerType(cit), "Expect pointer on stack for dest of stobj");
#endif // _DEBUG
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_StObj]);
#endif // INTERP_TRACING
CORINFO_CLASS_HANDLE clsHnd = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_StObj));
DWORD clsAttribs;
{
GCX_PREEMP();
clsAttribs = m_interpCeeInfo.getClassAttribs(clsHnd);
}
if (clsAttribs & CORINFO_FLG_VALUECLASS)
{
MethodTable* clsMT = GetMethodTableFromClsHnd(clsHnd);
size_t sz;
{
GCX_PREEMP();
sz = getClassSize(clsHnd);
}
// Note that "dest" might be a pointer into the heap. It is therefore important
// to calculate it *after* any PREEMP transitions at which we might do a GC.
void* dest = OpStackGet<void*>(destInd);
ThrowOnInvalidPointer(dest);
#ifdef _DEBUG
// Try and validate types
InterpreterType vit = OpStackTypeGet(valInd);
CorInfoType vitc = vit.ToCorInfoType();
if (vitc == CORINFO_TYPE_VALUECLASS)
{
CORINFO_CLASS_HANDLE vClsHnd = vit.ToClassHandle();
const bool isClass = (vClsHnd == clsHnd);
const bool isPrim = (vitc == CorInfoTypeStackNormalize(GetTypeForPrimitiveValueClass(clsHnd)));
bool isShared = false;
// If operand type is shared we need a more complex check;
// the IL type may not be shared
if (!isPrim && !isClass)
{
DWORD vClsAttribs;
{
GCX_PREEMP();
vClsAttribs = m_interpCeeInfo.getClassAttribs(vClsHnd);
}
if ((vClsAttribs & CORINFO_FLG_SHAREDINST) != 0)
{
MethodTable* clsMT2 = clsMT->GetCanonicalMethodTable();
if (((CORINFO_CLASS_HANDLE) clsMT2) == vClsHnd)
{
isShared = true;
}
}
}
_ASSERTE(isClass || isPrim || isShared);
}
else
{
const bool isSz = s_InterpreterLooseRules && sz <= sizeof(dest);
_ASSERTE(isSz);
}
#endif // _DEBUG
GCX_FORBID();
if (sz > sizeof(INT64))
{
// Large struct case -- ostack entry is pointer.
void* src = OpStackGet<void*>(valInd);
CopyValueClassUnchecked(dest, src, clsMT);
LargeStructOperandStackPop(sz, src);
}
else
{
// The ostack entry contains the struct value.
CopyValueClassUnchecked(dest, OpStackGetAddr(valInd, sz), clsMT);
}
}
else
{
// The ostack entry is an object reference.
_ASSERTE(OpStackTypeGet(valInd).ToCorInfoType() == CORINFO_TYPE_CLASS);
// Note that "dest" might be a pointer into the heap. It is therefore important
// to calculate it *after* any PREEMP transitions at which we might do a GC. (Thus,
// we have to duplicate this code with the case above.
void* dest = OpStackGet<void*>(destInd);
ThrowOnInvalidPointer(dest);
GCX_FORBID();
OBJECTREF val = ObjectToOBJECTREF(OpStackGet<Object*>(valInd));
SetObjectReference(reinterpret_cast<OBJECTREF*>(dest), val);
}
m_curStackHt -= 2;
m_ILCodePtr += 5;
BarrierIfVolatile();
}
void Interpreter::InitObj()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 1);
unsigned destInd = m_curStackHt - 1;
#ifdef _DEBUG
// Check that src and dest are both pointer types.
CorInfoType cit = OpStackTypeGet(destInd).ToCorInfoType();
_ASSERTE_MSG(IsValidPointerType(cit), "Expect pointer on stack");
#endif // _DEBUG
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_InitObj]);
#endif // INTERP_TRACING
CORINFO_CLASS_HANDLE clsHnd = GetTypeFromToken(m_ILCodePtr + 2, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_InitObj));
size_t valueClassSz = 0;
DWORD clsAttribs;
{
GCX_PREEMP();
clsAttribs = m_interpCeeInfo.getClassAttribs(clsHnd);
if (clsAttribs & CORINFO_FLG_VALUECLASS)
{
valueClassSz = getClassSize(clsHnd);
}
}
void* dest = OpStackGet<void*>(destInd);
ThrowOnInvalidPointer(dest);
// dest is a vulnerable byref.
GCX_FORBID();
if (clsAttribs & CORINFO_FLG_VALUECLASS)
{
memset(dest, 0, valueClassSz);
}
else
{
// The ostack entry is an object reference.
SetObjectReference(reinterpret_cast<OBJECTREF*>(dest), NULL);
}
m_curStackHt -= 1;
m_ILCodePtr += 6;
}
void Interpreter::LdStr()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
OBJECTHANDLE res = ConstructStringLiteral(m_methInfo->m_module, getU4LittleEndian(m_ILCodePtr + 1));
{
GCX_FORBID();
OpStackSet<Object*>(m_curStackHt, *reinterpret_cast<Object**>(res));
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_CLASS)); // Stack-normal type for "string"
m_curStackHt++;
}
m_ILCodePtr += 5;
}
void Interpreter::NewObj()
{
#if INTERP_DYNAMIC_CONTRACTS
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#else
// Dynamic contract occupies too much stack.
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_COOPERATIVE;
#endif
unsigned ctorTok = getU4LittleEndian(m_ILCodePtr + 1);
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_NewObj]);
#endif // INTERP_TRACING
CORINFO_CALL_INFO callInfo;
CORINFO_RESOLVED_TOKEN methTok;
{
GCX_PREEMP();
ResolveToken(&methTok, ctorTok, CORINFO_TOKENKIND_Ldtoken InterpTracingArg(RTK_NewObj));
m_interpCeeInfo.getCallInfo(&methTok, NULL,
m_methInfo->m_method,
CORINFO_CALLINFO_FLAGS(0),
&callInfo);
}
unsigned mflags = callInfo.methodFlags;
if ((mflags & (CORINFO_FLG_STATIC|CORINFO_FLG_ABSTRACT)) != 0)
{
VerificationError("newobj on static or abstract method");
}
unsigned clsFlags = callInfo.classFlags;
#ifdef _DEBUG
// What class are we allocating?
const char* clsName;
{
GCX_PREEMP();
clsName = m_interpCeeInfo.getClassNameFromMetadata(methTok.hClass, NULL);
}
#endif // _DEBUG
// There are four cases:
// 1) Value types (ordinary constructor, resulting VALUECLASS pushed)
// 2) String (var-args constructor, result automatically pushed)
// 3) MDArray (var-args constructor, resulting OBJECTREF pushed)
// 4) Reference types (ordinary constructor, resulting OBJECTREF pushed)
if (clsFlags & CORINFO_FLG_VALUECLASS)
{
void* tempDest;
INT64 smallTempDest = 0;
size_t sz = 0;
{
GCX_PREEMP();
sz = getClassSize(methTok.hClass);
}
if (sz > sizeof(INT64))
{
// TODO: Make sure this is deleted in the face of exceptions.
tempDest = new BYTE[sz];
}
else
{
tempDest = &smallTempDest;
}
memset(tempDest, 0, sz);
InterpreterType structValRetIT(&m_interpCeeInfo, methTok.hClass);
m_structRetValITPtr = &structValRetIT;
m_structRetValTempSpace = tempDest;
DoCallWork(/*virtCall*/false, tempDest, &methTok, &callInfo);
if (sz > sizeof(INT64))
{
void* dest = LargeStructOperandStackPush(sz);
memcpy(dest, tempDest, sz);
delete[] reinterpret_cast<BYTE*>(tempDest);
OpStackSet<void*>(m_curStackHt, dest);
}
else
{
OpStackSet<INT64>(m_curStackHt, GetSmallStructValue(tempDest, sz));
}
if (m_structRetValITPtr->IsStruct())
{
OpStackTypeSet(m_curStackHt, *m_structRetValITPtr);
}
else
{
// Must stack-normalize primitive types.
OpStackTypeSet(m_curStackHt, m_structRetValITPtr->StackNormalize());
}
// "Unregister" the temp space for GC scanning...
m_structRetValITPtr = NULL;
m_curStackHt++;
}
else if ((clsFlags & CORINFO_FLG_VAROBJSIZE) && !(clsFlags & CORINFO_FLG_ARRAY))
{
// For a VAROBJSIZE class (currently == String), pass NULL as this to "pseudo-constructor."
void* specialFlagArg = reinterpret_cast<void*>(0x1); // Special value for "thisArg" argument of "DoCallWork": pushing NULL that's not on op stack is unnecessary.(#62936)
DoCallWork(/*virtCall*/false, specialFlagArg, &methTok, &callInfo); // pushes result automatically
}
else
{
OBJECTREF thisArgObj = NULL;
GCPROTECT_BEGIN(thisArgObj);
if (clsFlags & CORINFO_FLG_ARRAY)
{
_ASSERTE(clsFlags & CORINFO_FLG_VAROBJSIZE);
MethodDesc* methDesc = GetMethod(methTok.hMethod);
PCCOR_SIGNATURE pSig;
DWORD cbSigSize;
methDesc->GetSig(&pSig, &cbSigSize);
MetaSig msig(pSig, cbSigSize, methDesc->GetModule(), NULL);
unsigned dwNumArgs = msig.NumFixedArgs();
_ASSERTE(m_curStackHt >= dwNumArgs);
m_curStackHt -= dwNumArgs;
INT32* args = (INT32*)_alloca(dwNumArgs * sizeof(INT32));
unsigned dwArg;
for (dwArg = 0; dwArg < dwNumArgs; dwArg++)
{
unsigned stkInd = m_curStackHt + dwArg;
bool loose = s_InterpreterLooseRules && (OpStackTypeGet(stkInd).ToCorInfoType() == CORINFO_TYPE_NATIVEINT);
if (OpStackTypeGet(stkInd).ToCorInfoType() != CORINFO_TYPE_INT && !loose)
{
VerificationError("MD array dimension bounds and sizes must be int.");
}
args[dwArg] = loose ? (INT32) OpStackGet<NativeInt>(stkInd) : OpStackGet<INT32>(stkInd);
}
thisArgObj = AllocateArrayEx(TypeHandle(methTok.hClass), args, dwNumArgs);
}
else
{
CorInfoHelpFunc newHelper;
{
GCX_PREEMP();
bool sideEffect;
newHelper = m_interpCeeInfo.getNewHelper(methTok.hClass, &sideEffect);
}
MethodTable * pNewObjMT = GetMethodTableFromClsHnd(methTok.hClass);
switch (newHelper)
{
case CORINFO_HELP_NEWFAST:
default:
thisArgObj = AllocateObject(pNewObjMT);
break;
}
DoCallWork(/*virtCall*/false, OBJECTREFToObject(thisArgObj), &methTok, &callInfo);
}
{
GCX_FORBID();
OpStackSet<Object*>(m_curStackHt, OBJECTREFToObject(thisArgObj));
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_CLASS));
m_curStackHt++;
}
GCPROTECT_END(); // For "thisArgObj"
}
m_ILCodePtr += 5;
}
void Interpreter::NewArr()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt > 0);
unsigned stkInd = m_curStackHt-1;
CorInfoType cit = OpStackTypeGet(stkInd).ToCorInfoType();
NativeInt sz = 0;
switch (cit)
{
case CORINFO_TYPE_INT:
sz = static_cast<NativeInt>(OpStackGet<INT32>(stkInd));
break;
case CORINFO_TYPE_NATIVEINT:
sz = OpStackGet<NativeInt>(stkInd);
break;
default:
VerificationError("Size operand of 'newarr' must be int or native int.");
}
unsigned elemTypeTok = getU4LittleEndian(m_ILCodePtr + 1);
CORINFO_CLASS_HANDLE elemClsHnd;
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_NewArr]);
#endif // INTERP_TRACING
CORINFO_RESOLVED_TOKEN elemTypeResolvedTok;
{
GCX_PREEMP();
ResolveToken(&elemTypeResolvedTok, elemTypeTok, CORINFO_TOKENKIND_Newarr InterpTracingArg(RTK_NewArr));
elemClsHnd = elemTypeResolvedTok.hClass;
}
{
if (sz < 0)
{
COMPlusThrow(kOverflowException);
}
#ifdef HOST_64BIT
// Even though ECMA allows using a native int as the argument to newarr instruction
// (therefore size is INT_PTR), ArrayBase::m_NumComponents is 32-bit, so even on 64-bit
// platforms we can't create an array whose size exceeds 32 bits.
if (sz > INT_MAX)
{
EX_THROW(EEMessageException, (kOverflowException, IDS_EE_ARRAY_DIMENSIONS_EXCEEDED));
}
#endif
TypeHandle th(elemClsHnd);
MethodTable* pArrayMT = th.GetMethodTable();
pArrayMT->CheckRunClassInitThrowing();
INT32 size32 = (INT32)sz;
Object* newarray = OBJECTREFToObject(AllocateSzArray(pArrayMT, size32));
GCX_FORBID();
OpStackTypeSet(stkInd, InterpreterType(CORINFO_TYPE_CLASS));
OpStackSet<Object*>(stkInd, newarray);
}
m_ILCodePtr += 5;
}
void Interpreter::IsInst()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_IsInst]);
#endif // INTERP_TRACING
CORINFO_CLASS_HANDLE cls = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Casting InterpTracingArg(RTK_IsInst));
_ASSERTE(m_curStackHt >= 1);
unsigned idx = m_curStackHt - 1;
#ifdef _DEBUG
CorInfoType cit = OpStackTypeGet(idx).ToCorInfoType();
_ASSERTE(cit == CORINFO_TYPE_CLASS || cit == CORINFO_TYPE_STRING);
#endif // DEBUG
Object * pObj = OpStackGet<Object*>(idx);
if (pObj != NULL)
{
if (!ObjIsInstanceOf(pObj, TypeHandle(cls)))
OpStackSet<Object*>(idx, NULL);
}
// Type stack stays unmodified.
m_ILCodePtr += 5;
}
void Interpreter::CastClass()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_CastClass]);
#endif // INTERP_TRACING
CORINFO_CLASS_HANDLE cls = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Casting InterpTracingArg(RTK_CastClass));
_ASSERTE(m_curStackHt >= 1);
unsigned idx = m_curStackHt - 1;
#ifdef _DEBUG
CorInfoType cit = OpStackTypeGet(idx).ToCorInfoType();
_ASSERTE(cit == CORINFO_TYPE_CLASS || cit == CORINFO_TYPE_STRING);
#endif // _DEBUG
Object * pObj = OpStackGet<Object*>(idx);
if (pObj != NULL)
{
if (!ObjIsInstanceOf(pObj, TypeHandle(cls), TRUE))
{
UNREACHABLE(); //ObjIsInstanceOf will throw if cast can't be done
}
}
// Type stack stays unmodified.
m_ILCodePtr += 5;
}
void Interpreter::LocAlloc()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 1);
unsigned idx = m_curStackHt - 1;
CorInfoType cit = OpStackTypeGet(idx).ToCorInfoType();
NativeUInt sz = 0;
if (cit == CORINFO_TYPE_INT || cit == CORINFO_TYPE_UINT)
{
sz = static_cast<NativeUInt>(OpStackGet<UINT32>(idx));
}
else if (cit == CORINFO_TYPE_NATIVEINT || cit == CORINFO_TYPE_NATIVEUINT)
{
sz = OpStackGet<NativeUInt>(idx);
}
else if (s_InterpreterLooseRules && cit == CORINFO_TYPE_LONG)
{
sz = (NativeUInt) OpStackGet<INT64>(idx);
}
else
{
VerificationError("localloc requires int or nativeint argument.");
}
if (sz == 0)
{
OpStackSet<void*>(idx, NULL);
}
else
{
void* res = GetLocAllocData()->Alloc(sz);
if (res == NULL) ThrowStackOverflow();
OpStackSet<void*>(idx, res);
}
OpStackTypeSet(idx, InterpreterType(CORINFO_TYPE_NATIVEINT));
}
void Interpreter::MkRefany()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_MkRefAny]);
#endif // INTERP_TRACING
CORINFO_CLASS_HANDLE cls = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_MkRefAny));
_ASSERTE(m_curStackHt >= 1);
unsigned idx = m_curStackHt - 1;
CorInfoType cit = OpStackTypeGet(idx).ToCorInfoType();
if (!(cit == CORINFO_TYPE_BYREF || cit == CORINFO_TYPE_NATIVEINT))
VerificationError("MkRefany requires byref or native int (pointer) on the stack.");
void* ptr = OpStackGet<void*>(idx);
InterpreterType typedRefIT = GetTypedRefIT(&m_interpCeeInfo);
TypedByRef* tbr;
#if defined(HOST_AMD64)
_ASSERTE(typedRefIT.IsLargeStruct(&m_interpCeeInfo));
tbr = (TypedByRef*) LargeStructOperandStackPush(GetTypedRefSize(&m_interpCeeInfo));
OpStackSet<void*>(idx, tbr);
#elif defined(HOST_X86) || defined(HOST_ARM)
_ASSERTE(!typedRefIT.IsLargeStruct(&m_interpCeeInfo));
tbr = OpStackGetAddr<TypedByRef>(idx);
#elif defined(HOST_ARM64)
tbr = NULL;
NYI_INTERP("Unimplemented code: MkRefAny");
#elif defined(HOST_LOONGARCH64)
tbr = NULL;
NYI_INTERP("Unimplemented code: MkRefAny on LOONGARCH");
#elif defined(HOST_RISCV64)
tbr = NULL;
NYI_INTERP("Unimplemented code: MkRefAny on RISCV64");
#else
#error "unsupported platform"
#endif
tbr->data = ptr;
tbr->type = TypeHandle(cls);
OpStackTypeSet(idx, typedRefIT);
m_ILCodePtr += 5;
}
void Interpreter::RefanyType()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt > 0);
unsigned idx = m_curStackHt - 1;
if (OpStackTypeGet(idx) != GetTypedRefIT(&m_interpCeeInfo))
VerificationError("RefAnyVal requires a TypedRef on the stack.");
TypedByRef* ptbr = OpStackGet<TypedByRef*>(idx);
LargeStructOperandStackPop(sizeof(TypedByRef), ptbr);
TypeHandle* pth = &ptbr->type;
{
OBJECTREF classobj = TypeHandleToTypeRef(pth);
GCX_FORBID();
OpStackSet<Object*>(idx, OBJECTREFToObject(classobj));
OpStackTypeSet(idx, InterpreterType(CORINFO_TYPE_CLASS));
}
m_ILCodePtr += 2;
}
// This (unfortunately) duplicates code in JIT_GetRuntimeTypeHandle, which
// isn't callable because it sets up a Helper Method Frame.
OBJECTREF Interpreter::TypeHandleToTypeRef(TypeHandle* pth)
{
OBJECTREF typePtr = NULL;
if (!pth->IsTypeDesc())
{
// Most common... and fastest case
typePtr = pth->AsMethodTable()->GetManagedClassObjectIfExists();
if (typePtr == NULL)
{
typePtr = pth->GetManagedClassObject();
}
}
else
{
typePtr = pth->GetManagedClassObject();
}
return typePtr;
}
CorInfoType Interpreter::GetTypeForPrimitiveValueClass(CORINFO_CLASS_HANDLE clsHnd)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
GCX_PREEMP();
return m_interpCeeInfo.getTypeForPrimitiveValueClass(clsHnd);
}
void Interpreter::RefanyVal()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt > 0);
unsigned idx = m_curStackHt - 1;
if (OpStackTypeGet(idx) != GetTypedRefIT(&m_interpCeeInfo))
VerificationError("RefAnyVal requires a TypedRef on the stack.");
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_RefAnyVal]);
#endif // INTERP_TRACING
CORINFO_CLASS_HANDLE cls = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_RefAnyVal));
TypeHandle expected(cls);
TypedByRef* ptbr = OpStackGet<TypedByRef*>(idx);
LargeStructOperandStackPop(sizeof(TypedByRef), ptbr);
if (expected != ptbr->type) ThrowInvalidCastException();
OpStackSet<void*>(idx, static_cast<void*>(ptbr->data));
OpStackTypeSet(idx, InterpreterType(CORINFO_TYPE_BYREF));
m_ILCodePtr += 5;
}
void Interpreter::CkFinite()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt > 0);
unsigned idx = m_curStackHt - 1;
CorInfoType cit = OpStackTypeGet(idx).ToCorInfoType();
double val = 0.0;
switch (cit)
{
case CORINFO_TYPE_FLOAT:
val = (double)OpStackGet<float>(idx);
break;
case CORINFO_TYPE_DOUBLE:
val = OpStackGet<double>(idx);
break;
default:
VerificationError("CkFinite requires a floating-point value on the stack.");
break;
}
if (!isfinite(val))
ThrowSysArithException();
}
void Interpreter::LdToken()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
unsigned tokVal = getU4LittleEndian(m_ILCodePtr + 1);
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdToken]);
#endif // INTERP_TRACING
CORINFO_RESOLVED_TOKEN tok;
{
GCX_PREEMP();
ResolveToken(&tok, tokVal, CORINFO_TOKENKIND_Ldtoken InterpTracingArg(RTK_LdToken));
}
// To save duplication of the factored code at the bottom, I don't do GCX_FORBID for
// these Object* values, but this comment documents the intent.
if (tok.hMethod != NULL)
{
MethodDesc* pMethod = (MethodDesc*)tok.hMethod;
Object* objPtr = OBJECTREFToObject((OBJECTREF)pMethod->GetStubMethodInfo());
OpStackSet<Object*>(m_curStackHt, objPtr);
}
else if (tok.hField != NULL)
{
FieldDesc * pField = (FieldDesc *)tok.hField;
Object* objPtr = OBJECTREFToObject((OBJECTREF)pField->GetStubFieldInfo());
OpStackSet<Object*>(m_curStackHt, objPtr);
}
else
{
TypeHandle th(tok.hClass);
Object* objPtr = OBJECTREFToObject(th.GetManagedClassObject());
OpStackSet<Object*>(m_curStackHt, objPtr);
}
{
GCX_FORBID();
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_CLASS));
m_curStackHt++;
}
m_ILCodePtr += 5;
}
void Interpreter::LdFtn()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
unsigned tokVal = getU4LittleEndian(m_ILCodePtr + 2);
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdFtn]);
#endif // INTERP_TRACING
CORINFO_RESOLVED_TOKEN tok;
CORINFO_CALL_INFO callInfo;
{
GCX_PREEMP();
ResolveToken(&tok, tokVal, CORINFO_TOKENKIND_Method InterpTracingArg(RTK_LdFtn));
m_interpCeeInfo.getCallInfo(&tok, NULL, m_methInfo->m_method,
combine(CORINFO_CALLINFO_SECURITYCHECKS,CORINFO_CALLINFO_LDFTN),
&callInfo);
}
switch (callInfo.kind)
{
case CORINFO_CALL:
{
PCODE pCode = ((MethodDesc *)callInfo.hMethod)->GetMultiCallableAddrOfCode();
OpStackSet<void*>(m_curStackHt, (void *)pCode);
GetFunctionPointerStack()[m_curStackHt] = callInfo.hMethod;
}
break;
case CORINFO_CALL_CODE_POINTER:
NYI_INTERP("Indirect code pointer.");
break;
default:
_ASSERTE_MSG(false, "Should not reach here: unknown call kind.");
break;
}
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_NATIVEINT));
m_curStackHt++;
m_ILCodePtr += 6;
}
void Interpreter::LdVirtFtn()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 1);
unsigned ind = m_curStackHt - 1;
unsigned tokVal = getU4LittleEndian(m_ILCodePtr + 2);
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdVirtFtn]);
#endif // INTERP_TRACING
CORINFO_RESOLVED_TOKEN tok;
CORINFO_CALL_INFO callInfo;
CORINFO_CLASS_HANDLE classHnd;
CORINFO_METHOD_HANDLE methodHnd;
{
GCX_PREEMP();
ResolveToken(&tok, tokVal, CORINFO_TOKENKIND_Method InterpTracingArg(RTK_LdVirtFtn));
m_interpCeeInfo.getCallInfo(&tok, NULL, m_methInfo->m_method,
combine(CORINFO_CALLINFO_CALLVIRT,
combine(CORINFO_CALLINFO_SECURITYCHECKS,
CORINFO_CALLINFO_LDFTN)),
&callInfo);
classHnd = tok.hClass;
methodHnd = tok.hMethod;
}
MethodDesc * pMD = (MethodDesc *)methodHnd;
PCODE pCode;
if (pMD->IsVtableMethod())
{
Object* obj = OpStackGet<Object*>(ind);
ThrowOnInvalidPointer(obj);
OBJECTREF objRef = ObjectToOBJECTREF(obj);
GCPROTECT_BEGIN(objRef);
pCode = pMD->GetMultiCallableAddrOfVirtualizedCode(&objRef, TypeHandle(classHnd));
GCPROTECT_END();
pMD = Entry2MethodDesc(pCode, TypeHandle(classHnd).GetMethodTable());
}
else
{
pCode = pMD->GetMultiCallableAddrOfCode();
}
OpStackSet<void*>(ind, (void *)pCode);
GetFunctionPointerStack()[ind] = (CORINFO_METHOD_HANDLE)pMD;
OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_NATIVEINT));
m_ILCodePtr += 6;
}
void Interpreter::Sizeof()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_Sizeof]);
#endif // INTERP_TRACING
CORINFO_CLASS_HANDLE cls = GetTypeFromToken(m_ILCodePtr + 2, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_Sizeof));
unsigned sz;
{
GCX_PREEMP();
CorInfoType cit = ::asCorInfoType(cls);
// For class types, the ECMA spec says to return the size of the object reference, not the referent
// object. Everything else should be a value type, for which we can just return the size as reported
// by the EE.
switch (cit)
{
case CORINFO_TYPE_CLASS:
sz = sizeof(Object*);
break;
default:
sz = getClassSize(cls);
break;
}
}
OpStackSet<UINT32>(m_curStackHt, sz);
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_INT));
m_curStackHt++;
m_ILCodePtr += 6;
}
// static:
bool Interpreter::s_initialized = false;
bool Interpreter::s_compilerStaticsInitialized = false;
size_t Interpreter::s_TypedRefSize;
CORINFO_CLASS_HANDLE Interpreter::s_TypedRefClsHnd;
InterpreterType Interpreter::s_TypedRefIT;
// Must call GetTypedRefIT
size_t Interpreter::GetTypedRefSize(CEEInfo* info)
{
_ASSERTE_MSG(s_compilerStaticsInitialized, "Precondition");
return s_TypedRefSize;
}
InterpreterType Interpreter::GetTypedRefIT(CEEInfo* info)
{
_ASSERTE_MSG(s_compilerStaticsInitialized, "Precondition");
return s_TypedRefIT;
}
CORINFO_CLASS_HANDLE Interpreter::GetTypedRefClsHnd(CEEInfo* info)
{
_ASSERTE_MSG(s_compilerStaticsInitialized, "Precondition");
return s_TypedRefClsHnd;
}
void Interpreter::Initialize()
{
_ASSERTE(!s_initialized);
s_InterpretMeths.ensureInit(CLRConfig::INTERNAL_Interpret);
s_InterpretMethsExclude.ensureInit(CLRConfig::INTERNAL_InterpretExclude);
s_InterpreterUseCaching = (s_InterpreterUseCachingFlag.val(CLRConfig::INTERNAL_InterpreterUseCaching) != 0);
s_InterpreterLooseRules = (s_InterpreterLooseRulesFlag.val(CLRConfig::INTERNAL_InterpreterLooseRules) != 0);
s_InterpreterDoLoopMethods = (s_InterpreterDoLoopMethodsFlag.val(CLRConfig::INTERNAL_InterpreterDoLoopMethods) != 0);
// Initialize the lock used to protect method locks.
// TODO: it would be better if this were a reader/writer lock.
s_methodCacheLock.Init(CrstLeafLock, CRST_DEFAULT);
// Similarly, initialize the lock used to protect the map from
// interpreter stub addresses to their method descs.
s_interpStubToMDMapLock.Init(CrstLeafLock, CRST_DEFAULT);
s_initialized = true;
#if INTERP_ILINSTR_PROFILE
SetILInstrCategories();
#endif // INTERP_ILINSTR_PROFILE
}
void Interpreter::InitializeCompilerStatics(CEEInfo* info)
{
if (!s_compilerStaticsInitialized)
{
// TODO: I believe I need no synchronization around this on x86, but I do
// on more permissive memory models. (Why it's OK on x86: each thread executes this
// before any access to the initialized static variables; if several threads do
// so, they perform idempotent initializing writes to the statics.
GCX_PREEMP();
s_TypedRefClsHnd = info->getBuiltinClass(CLASSID_TYPED_BYREF);
s_TypedRefIT = InterpreterType(info, s_TypedRefClsHnd);
s_TypedRefSize = getClassSize(s_TypedRefClsHnd);
s_compilerStaticsInitialized = true;
// TODO: Need store-store memory barrier here.
}
}
void Interpreter::Terminate()
{
if (s_initialized)
{
s_methodCacheLock.Destroy();
s_interpStubToMDMapLock.Destroy();
s_initialized = false;
}
}
#if INTERP_ILINSTR_PROFILE
void Interpreter::SetILInstrCategories()
{
// Start with the indentity maps
for (unsigned short instr = 0; instr < 512; instr++) s_ILInstrCategories[instr] = instr;
// Now make exceptions.
for (unsigned instr = CEE_LDARG_0; instr <= CEE_LDARG_3; instr++) s_ILInstrCategories[instr] = CEE_LDARG;
s_ILInstrCategories[CEE_LDARG_S] = CEE_LDARG;
for (unsigned instr = CEE_LDLOC_0; instr <= CEE_LDLOC_3; instr++) s_ILInstrCategories[instr] = CEE_LDLOC;
s_ILInstrCategories[CEE_LDLOC_S] = CEE_LDLOC;
for (unsigned instr = CEE_STLOC_0; instr <= CEE_STLOC_3; instr++) s_ILInstrCategories[instr] = CEE_STLOC;
s_ILInstrCategories[CEE_STLOC_S] = CEE_STLOC;
s_ILInstrCategories[CEE_LDLOCA_S] = CEE_LDLOCA;
for (unsigned instr = CEE_LDC_I4_M1; instr <= CEE_LDC_I4_S; instr++) s_ILInstrCategories[instr] = CEE_LDC_I4;
for (unsigned instr = CEE_BR_S; instr <= CEE_BLT_UN; instr++) s_ILInstrCategories[instr] = CEE_BR;
for (unsigned instr = CEE_LDIND_I1; instr <= CEE_LDIND_REF; instr++) s_ILInstrCategories[instr] = CEE_LDIND_I;
for (unsigned instr = CEE_STIND_REF; instr <= CEE_STIND_R8; instr++) s_ILInstrCategories[instr] = CEE_STIND_I;
for (unsigned instr = CEE_ADD; instr <= CEE_REM_UN; instr++) s_ILInstrCategories[instr] = CEE_ADD;
for (unsigned instr = CEE_AND; instr <= CEE_NOT; instr++) s_ILInstrCategories[instr] = CEE_AND;
for (unsigned instr = CEE_CONV_I1; instr <= CEE_CONV_U8; instr++) s_ILInstrCategories[instr] = CEE_CONV_I;
for (unsigned instr = CEE_CONV_OVF_I1_UN; instr <= CEE_CONV_OVF_U_UN; instr++) s_ILInstrCategories[instr] = CEE_CONV_I;
for (unsigned instr = CEE_LDELEM_I1; instr <= CEE_LDELEM_REF; instr++) s_ILInstrCategories[instr] = CEE_LDELEM;
for (unsigned instr = CEE_STELEM_I; instr <= CEE_STELEM_REF; instr++) s_ILInstrCategories[instr] = CEE_STELEM;
for (unsigned instr = CEE_CONV_OVF_I1; instr <= CEE_CONV_OVF_U8; instr++) s_ILInstrCategories[instr] = CEE_CONV_I;
for (unsigned instr = CEE_CONV_U2; instr <= CEE_CONV_U1; instr++) s_ILInstrCategories[instr] = CEE_CONV_I;
for (unsigned instr = CEE_CONV_OVF_I; instr <= CEE_CONV_OVF_U; instr++) s_ILInstrCategories[instr] = CEE_CONV_I;
for (unsigned instr = CEE_ADD_OVF; instr <= CEE_SUB_OVF; instr++) s_ILInstrCategories[instr] = CEE_ADD_OVF;
s_ILInstrCategories[CEE_LEAVE_S] = CEE_LEAVE;
s_ILInstrCategories[CEE_CONV_U] = CEE_CONV_I;
}
#endif // INTERP_ILINSTR_PROFILE
#ifndef TARGET_WINDOWS
namespace
{
bool isnan(float val)
{
UINT32 bits = *reinterpret_cast<UINT32*>(&val);
return (bits & 0x7FFFFFFFU) > 0x7F800000U;
}
}
#endif
template<int op>
void Interpreter::CompareOp()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 2);
unsigned op1idx = m_curStackHt - 2;
INT32 res = CompareOpRes<op>(op1idx);
OpStackSet<INT32>(op1idx, res);
OpStackTypeSet(op1idx, InterpreterType(CORINFO_TYPE_INT));
m_curStackHt--;
}
template<int op>
INT32 Interpreter::CompareOpRes(unsigned op1idx)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= op1idx + 2);
unsigned op2idx = op1idx + 1;
InterpreterType t1 = OpStackTypeGet(op1idx);
CorInfoType cit1 = t1.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit1));
InterpreterType t2 = OpStackTypeGet(op2idx);
CorInfoType cit2 = t2.ToCorInfoType();
_ASSERTE(IsStackNormalType(cit2));
INT32 res = 0;
switch (cit1)
{
case CORINFO_TYPE_INT:
if (cit2 == CORINFO_TYPE_INT)
{
INT32 val1 = OpStackGet<INT32>(op1idx);
INT32 val2 = OpStackGet<INT32>(op2idx);
if (op == CO_EQ)
{
if (val1 == val2) res = 1;
}
else if (op == CO_GT)
{
if (val1 > val2) res = 1;
}
else if (op == CO_GT_UN)
{
if (static_cast<UINT32>(val1) > static_cast<UINT32>(val2)) res = 1;
}
else if (op == CO_LT)
{
if (val1 < val2) res = 1;
}
else
{
_ASSERTE(op == CO_LT_UN);
if (static_cast<UINT32>(val1) < static_cast<UINT32>(val2)) res = 1;
}
}
else if (cit2 == CORINFO_TYPE_NATIVEINT ||
(s_InterpreterLooseRules && cit2 == CORINFO_TYPE_BYREF) ||
(cit2 == CORINFO_TYPE_VALUECLASS
&& CorInfoTypeStackNormalize(GetTypeForPrimitiveValueClass(t2.ToClassHandle())) == CORINFO_TYPE_NATIVEINT))
{
NativeInt val1 = OpStackGet<NativeInt>(op1idx);
NativeInt val2 = OpStackGet<NativeInt>(op2idx);
if (op == CO_EQ)
{
if (val1 == val2) res = 1;
}
else if (op == CO_GT)
{
if (val1 > val2) res = 1;
}
else if (op == CO_GT_UN)
{
if (static_cast<NativeUInt>(val1) > static_cast<NativeUInt>(val2)) res = 1;
}
else if (op == CO_LT)
{
if (val1 < val2) res = 1;
}
else
{
_ASSERTE(op == CO_LT_UN);
if (static_cast<NativeUInt>(val1) < static_cast<NativeUInt>(val2)) res = 1;
}
}
else if (cit2 == CORINFO_TYPE_VALUECLASS)
{
cit2 = GetTypeForPrimitiveValueClass(t2.ToClassHandle());
INT32 val1 = OpStackGet<INT32>(op1idx);
INT32 val2 = 0;
if (CorInfoTypeStackNormalize(cit2) == CORINFO_TYPE_INT)
{
size_t sz = t2.Size(&m_interpCeeInfo);
switch (sz)
{
case 1:
if (CorInfoTypeIsUnsigned(cit2))
{
val2 = OpStackGet<UINT8>(op2idx);
}
else
{
val2 = OpStackGet<INT8>(op2idx);
}
break;
case 2:
if (CorInfoTypeIsUnsigned(cit2))
{
val2 = OpStackGet<UINT16>(op2idx);
}
else
{
val2 = OpStackGet<INT16>(op2idx);
}
break;
case 4:
val2 = OpStackGet<INT32>(op2idx);
break;
default:
UNREACHABLE();
}
}
else
{
VerificationError("Can't compare with struct type.");
}
if (op == CO_EQ)
{
if (val1 == val2) res = 1;
}
else if (op == CO_GT)
{
if (val1 > val2) res = 1;
}
else if (op == CO_GT_UN)
{
if (static_cast<UINT32>(val1) > static_cast<UINT32>(val2)) res = 1;
}
else if (op == CO_LT)
{
if (val1 < val2) res = 1;
}
else
{
_ASSERTE(op == CO_LT_UN);
if (static_cast<UINT32>(val1) < static_cast<UINT32>(val2)) res = 1;
}
}
else
{
VerificationError("Binary comparison operation: type mismatch.");
}
break;
case CORINFO_TYPE_NATIVEINT:
if (cit2 == CORINFO_TYPE_NATIVEINT || cit2 == CORINFO_TYPE_INT
|| (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_LONG)
|| (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_BYREF)
|| (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_CLASS && OpStackGet<void*>(op2idx) == 0))
{
NativeInt val1 = OpStackGet<NativeInt>(op1idx);
NativeInt val2;
if (cit2 == CORINFO_TYPE_NATIVEINT)
{
val2 = OpStackGet<NativeInt>(op2idx);
}
else if (cit2 == CORINFO_TYPE_INT)
{
val2 = static_cast<NativeInt>(OpStackGet<INT32>(op2idx));
}
else if (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_LONG)
{
val2 = static_cast<NativeInt>(OpStackGet<INT64>(op2idx));
}
else if (cit2 == CORINFO_TYPE_CLASS)
{
_ASSERTE(OpStackGet<void*>(op2idx) == 0);
val2 = 0;
}
else
{
_ASSERTE(s_InterpreterLooseRules && cit2 == CORINFO_TYPE_BYREF);
val2 = reinterpret_cast<NativeInt>(OpStackGet<void*>(op2idx));
}
if (op == CO_EQ)
{
if (val1 == val2) res = 1;
}
else if (op == CO_GT)
{
if (val1 > val2) res = 1;
}
else if (op == CO_GT_UN)
{
if (static_cast<NativeUInt>(val1) > static_cast<NativeUInt>(val2)) res = 1;
}
else if (op == CO_LT)
{
if (val1 < val2) res = 1;
}
else
{
_ASSERTE(op == CO_LT_UN);
if (static_cast<NativeUInt>(val1) < static_cast<NativeUInt>(val2)) res = 1;
}
}
else
{
VerificationError("Binary comparison operation: type mismatch.");
}
break;
case CORINFO_TYPE_LONG:
{
bool looseLong = false;
#if defined(HOST_AMD64)
looseLong = s_InterpreterLooseRules && (cit2 == CORINFO_TYPE_NATIVEINT || cit2 == CORINFO_TYPE_BYREF);
#endif
if (cit2 == CORINFO_TYPE_LONG || looseLong)
{
INT64 val1 = OpStackGet<INT64>(op1idx);
INT64 val2 = OpStackGet<INT64>(op2idx);
if (op == CO_EQ)
{
if (val1 == val2) res = 1;
}
else if (op == CO_GT)
{
if (val1 > val2) res = 1;
}
else if (op == CO_GT_UN)
{
if (static_cast<UINT64>(val1) > static_cast<UINT64>(val2)) res = 1;
}
else if (op == CO_LT)
{
if (val1 < val2) res = 1;
}
else
{
_ASSERTE(op == CO_LT_UN);
if (static_cast<UINT64>(val1) < static_cast<UINT64>(val2)) res = 1;
}
}
else
{
VerificationError("Binary comparison operation: type mismatch.");
}
}
break;
case CORINFO_TYPE_CLASS:
case CORINFO_TYPE_STRING:
if (cit2 == CORINFO_TYPE_CLASS || cit2 == CORINFO_TYPE_STRING)
{
GCX_FORBID();
Object* val1 = OpStackGet<Object*>(op1idx);
Object* val2 = OpStackGet<Object*>(op2idx);
if (op == CO_EQ)
{
if (val1 == val2) res = 1;
}
else if (op == CO_GT_UN)
{
if (val1 != val2) res = 1;
}
else
{
VerificationError("Binary comparison operation: type mismatch.");
}
}
else
{
VerificationError("Binary comparison operation: type mismatch.");
}
break;
case CORINFO_TYPE_FLOAT:
{
bool isDouble = (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_DOUBLE);
if (cit2 == CORINFO_TYPE_FLOAT || isDouble)
{
float val1 = OpStackGet<float>(op1idx);
float val2 = (isDouble) ? (float) OpStackGet<double>(op2idx) : OpStackGet<float>(op2idx);
if (op == CO_EQ)
{
// I'm assuming IEEE math here, so that if at least one is a NAN, the comparison will fail...
if (val1 == val2) res = 1;
}
else if (op == CO_GT)
{
// I'm assuming that C++ arithmetic does the right thing here with infinities and NANs.
if (val1 > val2) res = 1;
}
else if (op == CO_GT_UN)
{
// Check for NAN's here: if either is a NAN, they're unordered, so this comparison returns true.
if (isnan(val1) || isnan(val2)) res = 1;
else if (val1 > val2) res = 1;
}
else if (op == CO_LT)
{
if (val1 < val2) res = 1;
}
else
{
_ASSERTE(op == CO_LT_UN);
// Check for NAN's here: if either is a NAN, they're unordered, so this comparison returns true.
if (isnan(val1) || isnan(val2)) res = 1;
else if (val1 < val2) res = 1;
}
}
else
{
VerificationError("Binary comparison operation: type mismatch.");
}
}
break;
case CORINFO_TYPE_DOUBLE:
{
bool isFloat = (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_FLOAT);
if (cit2 == CORINFO_TYPE_DOUBLE || isFloat)
{
double val1 = OpStackGet<double>(op1idx);
double val2 = (isFloat) ? (double) OpStackGet<float>(op2idx) : OpStackGet<double>(op2idx);
if (op == CO_EQ)
{
// I'm assuming IEEE math here, so that if at least one is a NAN, the comparison will fail...
if (val1 == val2) res = 1;
}
else if (op == CO_GT)
{
// I'm assuming that C++ arithmetic does the right thing here with infinities and NANs.
if (val1 > val2) res = 1;
}
else if (op == CO_GT_UN)
{
// Check for NAN's here: if either is a NAN, they're unordered, so this comparison returns true.
if (isnan(val1) || isnan(val2)) res = 1;
else if (val1 > val2) res = 1;
}
else if (op == CO_LT)
{
if (val1 < val2) res = 1;
}
else
{
_ASSERTE(op == CO_LT_UN);
// Check for NAN's here: if either is a NAN, they're unordered, so this comparison returns true.
if (isnan(val1) || isnan(val2)) res = 1;
else if (val1 < val2) res = 1;
}
}
else
{
VerificationError("Binary comparison operation: type mismatch.");
}
}
break;
case CORINFO_TYPE_BYREF:
if (cit2 == CORINFO_TYPE_BYREF || (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_NATIVEINT))
{
NativeInt val1 = reinterpret_cast<NativeInt>(OpStackGet<void*>(op1idx));
NativeInt val2;
if (cit2 == CORINFO_TYPE_BYREF)
{
val2 = reinterpret_cast<NativeInt>(OpStackGet<void*>(op2idx));
}
else
{
_ASSERTE(s_InterpreterLooseRules && cit2 == CORINFO_TYPE_NATIVEINT);
val2 = OpStackGet<NativeInt>(op2idx);
}
if (op == CO_EQ)
{
if (val1 == val2) res = 1;
}
else if (op == CO_GT)
{
if (val1 > val2) res = 1;
}
else if (op == CO_GT_UN)
{
if (static_cast<NativeUInt>(val1) > static_cast<NativeUInt>(val2)) res = 1;
}
else if (op == CO_LT)
{
if (val1 < val2) res = 1;
}
else
{
_ASSERTE(op == CO_LT_UN);
if (static_cast<NativeUInt>(val1) < static_cast<NativeUInt>(val2)) res = 1;
}
}
else
{
VerificationError("Binary comparison operation: type mismatch.");
}
break;
case CORINFO_TYPE_VALUECLASS:
{
CorInfoType newCit1 = GetTypeForPrimitiveValueClass(t1.ToClassHandle());
if (newCit1 == CORINFO_TYPE_UNDEF)
{
VerificationError("Can't compare a value class.");
}
else
{
NYI_INTERP("Must eliminate 'punning' value classes from the ostack.");
}
}
break;
default:
_ASSERTE(false); // Should not be here if the type is stack-normal.
}
return res;
}
template<bool val, int targetLen>
void Interpreter::BrOnValue()
{
_ASSERTE(targetLen == 1 || targetLen == 4);
_ASSERTE(m_curStackHt > 0);
unsigned stackInd = m_curStackHt - 1;
InterpreterType it = OpStackTypeGet(stackInd);
// It shouldn't be a value class, unless it's a punning name for a primitive integral type.
if (it.ToCorInfoType() == CORINFO_TYPE_VALUECLASS)
{
GCX_PREEMP();
CorInfoType cit = m_interpCeeInfo.getTypeForPrimitiveValueClass(it.ToClassHandle());
if (CorInfoTypeIsIntegral(cit))
{
it = InterpreterType(cit);
}
else
{
VerificationError("Can't branch on the value of a value type that is not a primitive type.");
}
}
#ifdef _DEBUG
switch (it.ToCorInfoType())
{
case CORINFO_TYPE_FLOAT:
case CORINFO_TYPE_DOUBLE:
VerificationError("Can't branch on the value of a float or double.");
break;
default:
break;
}
#endif // _DEBUG
switch (it.SizeNotStruct())
{
case 4:
{
INT32 branchVal = OpStackGet<INT32>(stackInd);
BrOnValueTakeBranch((branchVal != 0) == val, targetLen);
}
break;
case 8:
{
INT64 branchVal = OpStackGet<INT64>(stackInd);
BrOnValueTakeBranch((branchVal != 0) == val, targetLen);
}
break;
// The value-class case handled above makes sizes 1 and 2 possible.
case 1:
{
INT8 branchVal = OpStackGet<INT8>(stackInd);
BrOnValueTakeBranch((branchVal != 0) == val, targetLen);
}
break;
case 2:
{
INT16 branchVal = OpStackGet<INT16>(stackInd);
BrOnValueTakeBranch((branchVal != 0) == val, targetLen);
}
break;
default:
UNREACHABLE();
break;
}
m_curStackHt = stackInd;
}
// compOp is a member of the BranchComparisonOp enumeration.
template<int compOp, bool reverse, int targetLen>
void Interpreter::BrOnComparison()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(targetLen == 1 || targetLen == 4);
_ASSERTE(m_curStackHt >= 2);
unsigned v1Ind = m_curStackHt - 2;
INT32 res = CompareOpRes<compOp>(v1Ind);
if (reverse)
{
res = (res == 0) ? 1 : 0;
}
if (res)
{
int offset;
if (targetLen == 1)
{
// BYTE is unsigned...
offset = getI1(m_ILCodePtr + 1);
}
else
{
offset = getI4LittleEndian(m_ILCodePtr + 1);
}
// 1 is the size of the current instruction; offset is relative to start of next.
if (offset < 0)
{
// Backwards branch; enable caching.
BackwardsBranchActions(offset);
}
ExecuteBranch(m_ILCodePtr + 1 + targetLen + offset);
}
else
{
m_ILCodePtr += targetLen + 1;
}
m_curStackHt -= 2;
}
void Interpreter::LdFld(FieldDesc* fldIn)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
BarrierIfVolatile();
FieldDesc* fld = fldIn;
CORINFO_CLASS_HANDLE valClsHnd = NULL;
DWORD fldOffset;
{
GCX_PREEMP();
unsigned ilOffset = CurOffset();
if (fld == NULL && s_InterpreterUseCaching)
{
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdFld]);
#endif // INTERP_TRACING
fld = GetCachedInstanceField(ilOffset);
}
if (fld == NULL)
{
unsigned tok = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));
fld = FindField(tok InterpTracingArg(RTK_LdFld));
_ASSERTE(fld != NULL);
fldOffset = fld->GetOffset();
if (s_InterpreterUseCaching && fldOffset < FIELD_OFFSET_LAST_REAL_OFFSET)
CacheInstanceField(ilOffset, fld);
}
else
{
fldOffset = fld->GetOffset();
}
}
CorInfoType valCit = CEEInfo::asCorInfoType(fld->GetFieldType());
// If "fldIn" is non-NULL, it's not a "real" LdFld -- the caller should handle updating the instruction pointer.
if (fldIn == NULL)
m_ILCodePtr += 5; // Last use above, so update now.
// We need to construct the interpreter type for a struct type before we try to do coordinated
// pushes of the value and type on the opstacks -- these must be atomic wrt GC, and constructing
// a struct InterpreterType transitions to preemptive mode.
InterpreterType structValIT;
if (valCit == CORINFO_TYPE_VALUECLASS)
{
GCX_PREEMP();
valCit = m_interpCeeInfo.getFieldType(CORINFO_FIELD_HANDLE(fld), &valClsHnd, nullptr);
structValIT = InterpreterType(&m_interpCeeInfo, valClsHnd);
}
UINT sz = fld->GetSize();
// Live vars: valCit, structValIt
_ASSERTE(m_curStackHt > 0);
unsigned stackInd = m_curStackHt - 1;
InterpreterType addrIt = OpStackTypeGet(stackInd);
CorInfoType addrCit = addrIt.ToCorInfoType();
bool isUnsigned;
if (addrCit == CORINFO_TYPE_CLASS)
{
OBJECTREF obj = OBJECTREF(OpStackGet<Object*>(stackInd));
ThrowOnInvalidPointer(OBJECTREFToObject(obj));
if (valCit == CORINFO_TYPE_VALUECLASS)
{
void* srcPtr = fld->GetInstanceAddress(obj);
// srcPtr is now vulnerable.
GCX_FORBID();
MethodTable* valClsMT = GetMethodTableFromClsHnd(valClsHnd);
if (sz > sizeof(INT64))
{
// Large struct case: allocate space on the large struct operand stack.
void* destPtr = LargeStructOperandStackPush(sz);
OpStackSet<void*>(stackInd, destPtr);
CopyValueClass(destPtr, srcPtr, valClsMT);
}
else
{
// Small struct case -- is inline in operand stack.
OpStackSet<INT64>(stackInd, GetSmallStructValue(srcPtr, sz));
}
}
else
{
BYTE* fldStart = dac_cast<PTR_BYTE>(OBJECTREFToObject(obj)) + sizeof(Object) + fldOffset;
// fldStart is now a vulnerable byref
GCX_FORBID();
switch (sz)
{
case 1:
isUnsigned = CorInfoTypeIsUnsigned(valCit);
if (isUnsigned)
{
OpStackSet<UINT32>(stackInd, *reinterpret_cast<UINT8*>(fldStart));
}
else
{
OpStackSet<INT32>(stackInd, *reinterpret_cast<INT8*>(fldStart));
}
break;
case 2:
isUnsigned = CorInfoTypeIsUnsigned(valCit);
if (isUnsigned)
{
OpStackSet<UINT32>(stackInd, *reinterpret_cast<UINT16*>(fldStart));
}
else
{
OpStackSet<INT32>(stackInd, *reinterpret_cast<INT16*>(fldStart));
}
break;
case 4:
OpStackSet<INT32>(stackInd, *reinterpret_cast<INT32*>(fldStart));
break;
case 8:
OpStackSet<INT64>(stackInd, *reinterpret_cast<INT64*>(fldStart));
break;
default:
_ASSERTE_MSG(false, "Should not reach here.");
break;
}
}
}
else
{
INT8* ptr = NULL;
if (addrCit == CORINFO_TYPE_VALUECLASS)
{
size_t addrSize = addrIt.Size(&m_interpCeeInfo);
// The ECMA spec allows ldfld to be applied to "an instance of a value type."
// We will take the address of the ostack entry.
if (addrIt.IsLargeStruct(&m_interpCeeInfo))
{
ptr = reinterpret_cast<INT8*>(OpStackGet<void*>(stackInd));
// This is delicate. I'm going to pop the large struct off the large-struct stack
// now, even though the field value we push may go back on the large object stack.
// We rely on the fact that this instruction doesn't do any other pushing, and
// we assume that LargeStructOperandStackPop does not actually deallocate any memory,
// and we rely on memcpy properly handling possibly-overlapping regions being copied.
// Finally (wow, this really *is* delicate), we rely on the property that the large-struct
// stack pop operation doesn't deallocate memory (the size of the allocated memory for the
// large-struct stack only grows in a method execution), and that if we push the field value
// on the large struct stack below, the size of the pushed item is at most the size of the
// popped item, so the stack won't grow (which would allow a dealloc/realloc).
// (All in all, maybe it would be better to just copy the value elsewhere then pop...but
// that wouldn't be very aggressive.)
LargeStructOperandStackPop(addrSize, ptr);
}
else
{
ptr = reinterpret_cast<INT8*>(OpStackGetAddr(stackInd, addrSize));
}
}
else
{
_ASSERTE(CorInfoTypeIsPointer(addrCit));
ptr = OpStackGet<INT8*>(stackInd);
ThrowOnInvalidPointer(ptr);
}
_ASSERTE(ptr != NULL);
ptr += fldOffset;
if (valCit == CORINFO_TYPE_VALUECLASS)
{
if (sz > sizeof(INT64))
{
// Large struct case.
void* dstPtr = LargeStructOperandStackPush(sz);
memcpy(dstPtr, ptr, sz);
OpStackSet<void*>(stackInd, dstPtr);
}
else
{
// Small struct case -- is inline in operand stack.
OpStackSet<INT64>(stackInd, GetSmallStructValue(ptr, sz));
}
OpStackTypeSet(stackInd, structValIT.StackNormalize());
return;
}
// Otherwise...
switch (sz)
{
case 1:
isUnsigned = CorInfoTypeIsUnsigned(valCit);
if (isUnsigned)
{
OpStackSet<UINT32>(stackInd, *reinterpret_cast<UINT8*>(ptr));
}
else
{
OpStackSet<INT32>(stackInd, *reinterpret_cast<INT8*>(ptr));
}
break;
case 2:
isUnsigned = CorInfoTypeIsUnsigned(valCit);
if (isUnsigned)
{
OpStackSet<UINT32>(stackInd, *reinterpret_cast<UINT16*>(ptr));
}
else
{
OpStackSet<INT32>(stackInd, *reinterpret_cast<INT16*>(ptr));
}
break;
case 4:
OpStackSet<INT32>(stackInd, *reinterpret_cast<INT32*>(ptr));
break;
case 8:
OpStackSet<INT64>(stackInd, *reinterpret_cast<INT64*>(ptr));
break;
}
}
if (valCit == CORINFO_TYPE_VALUECLASS)
{
OpStackTypeSet(stackInd, structValIT.StackNormalize());
}
else
{
OpStackTypeSet(stackInd, InterpreterType(valCit).StackNormalize());
}
}
void Interpreter::LdFldA()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
unsigned tok = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdFldA]);
#endif // INTERP_TRACING
unsigned offset = CurOffset();
m_ILCodePtr += 5; // Last use above, so update now.
FieldDesc* fld = NULL;
if (s_InterpreterUseCaching) fld = GetCachedInstanceField(offset);
if (fld == NULL)
{
GCX_PREEMP();
fld = FindField(tok InterpTracingArg(RTK_LdFldA));
if (s_InterpreterUseCaching) CacheInstanceField(offset, fld);
}
_ASSERTE(m_curStackHt > 0);
unsigned stackInd = m_curStackHt - 1;
CorInfoType addrCit = OpStackTypeGet(stackInd).ToCorInfoType();
if (addrCit == CORINFO_TYPE_BYREF || addrCit == CORINFO_TYPE_CLASS || addrCit == CORINFO_TYPE_NATIVEINT)
{
NativeInt ptr = OpStackGet<NativeInt>(stackInd);
ThrowOnInvalidPointer((void*)ptr);
// The "offset" below does not include the Object (i.e., the MethodTable pointer) for object pointers, so add that in first.
if (addrCit == CORINFO_TYPE_CLASS) ptr += sizeof(Object);
// Now add the offset.
ptr += fld->GetOffset();
OpStackSet<NativeInt>(stackInd, ptr);
if (addrCit == CORINFO_TYPE_NATIVEINT)
{
OpStackTypeSet(stackInd, InterpreterType(CORINFO_TYPE_NATIVEINT));
}
else
{
OpStackTypeSet(stackInd, InterpreterType(CORINFO_TYPE_BYREF));
}
}
else
{
VerificationError("LdfldA requires object reference, managed or unmanaged pointer type.");
}
}
void Interpreter::StFld()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_StFld]);
#endif // INTERP_TRACING
FieldDesc* fld = NULL;
DWORD fldOffset;
{
unsigned ilOffset = CurOffset();
if (s_InterpreterUseCaching) fld = GetCachedInstanceField(ilOffset);
if (fld == NULL)
{
unsigned tok = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));
GCX_PREEMP();
fld = FindField(tok InterpTracingArg(RTK_StFld));
_ASSERTE(fld != NULL);
fldOffset = fld->GetOffset();
if (s_InterpreterUseCaching && fldOffset < FIELD_OFFSET_LAST_REAL_OFFSET)
CacheInstanceField(ilOffset, fld);
}
else
{
fldOffset = fld->GetOffset();
}
}
m_ILCodePtr += 5; // Last use above, so update now.
UINT sz = fld->GetSize();
_ASSERTE(m_curStackHt >= 2);
unsigned addrInd = m_curStackHt - 2;
CorInfoType addrCit = OpStackTypeGet(addrInd).ToCorInfoType();
unsigned valInd = m_curStackHt - 1;
CorInfoType valCit = OpStackTypeGet(valInd).ToCorInfoType();
_ASSERTE(IsStackNormalType(addrCit) && IsStackNormalType(valCit));
m_curStackHt -= 2;
if (addrCit == CORINFO_TYPE_CLASS)
{
OBJECTREF obj = OBJECTREF(OpStackGet<Object*>(addrInd));
ThrowOnInvalidPointer(OBJECTREFToObject(obj));
if (valCit == CORINFO_TYPE_CLASS)
{
fld->SetRefValue(obj, ObjectToOBJECTREF(OpStackGet<Object*>(valInd)));
}
else if (valCit == CORINFO_TYPE_VALUECLASS)
{
MethodTable* valClsMT = GetMethodTableFromClsHnd(OpStackTypeGet(valInd).ToClassHandle());
void* destPtr = fld->GetInstanceAddress(obj);
// destPtr is now a vulnerable byref, so can't do GC.
GCX_FORBID();
// I use GCSafeMemCpy below to ensure that write barriers happen for the case in which
// the value class contains GC pointers. We could do better...
if (sz > sizeof(INT64))
{
// Large struct case: stack slot contains pointer...
void* srcPtr = OpStackGet<void*>(valInd);
CopyValueClassUnchecked(destPtr, srcPtr, valClsMT);
LargeStructOperandStackPop(sz, srcPtr);
}
else
{
// Small struct case -- is inline in operand stack.
CopyValueClassUnchecked(destPtr, OpStackGetAddr(valInd, sz), valClsMT);
}
BarrierIfVolatile();
return;
}
else
{
BYTE* fldStart = dac_cast<PTR_BYTE>(OBJECTREFToObject(obj)) + sizeof(Object) + fldOffset;
// fldStart is now a vulnerable byref
GCX_FORBID();
switch (sz)
{
case 1:
*reinterpret_cast<INT8*>(fldStart) = OpStackGet<INT8>(valInd);
break;
case 2:
*reinterpret_cast<INT16*>(fldStart) = OpStackGet<INT16>(valInd);
break;
case 4:
*reinterpret_cast<INT32*>(fldStart) = OpStackGet<INT32>(valInd);
break;
case 8:
*reinterpret_cast<INT64*>(fldStart) = OpStackGet<INT64>(valInd);
break;
}
}
}
else
{
_ASSERTE(addrCit == CORINFO_TYPE_BYREF || addrCit == CORINFO_TYPE_NATIVEINT);
INT8* destPtr = OpStackGet<INT8*>(addrInd);
ThrowOnInvalidPointer(destPtr);
destPtr += fldOffset;
if (valCit == CORINFO_TYPE_VALUECLASS)
{
MethodTable* valClsMT = GetMethodTableFromClsHnd(OpStackTypeGet(valInd).ToClassHandle());
// I use GCSafeMemCpy below to ensure that write barriers happen for the case in which
// the value class contains GC pointers. We could do better...
if (sz > sizeof(INT64))
{
// Large struct case: stack slot contains pointer...
void* srcPtr = OpStackGet<void*>(valInd);
CopyValueClassUnchecked(destPtr, srcPtr, valClsMT);
LargeStructOperandStackPop(sz, srcPtr);
}
else
{
// Small struct case -- is inline in operand stack.
CopyValueClassUnchecked(destPtr, OpStackGetAddr(valInd, sz), valClsMT);
}
BarrierIfVolatile();
return;
}
else if (valCit == CORINFO_TYPE_CLASS)
{
OBJECTREF val = ObjectToOBJECTREF(OpStackGet<Object*>(valInd));
SetObjectReference(reinterpret_cast<OBJECTREF*>(destPtr), val);
}
else
{
switch (sz)
{
case 1:
*reinterpret_cast<INT8*>(destPtr) = OpStackGet<INT8>(valInd);
break;
case 2:
*reinterpret_cast<INT16*>(destPtr) = OpStackGet<INT16>(valInd);
break;
case 4:
*reinterpret_cast<INT32*>(destPtr) = OpStackGet<INT32>(valInd);
break;
case 8:
*reinterpret_cast<INT64*>(destPtr) = OpStackGet<INT64>(valInd);
break;
}
}
}
BarrierIfVolatile();
}
bool Interpreter::StaticFldAddrWork(CORINFO_ACCESS_FLAGS accessFlgs, /*out (byref)*/void** pStaticFieldAddr, /*out*/InterpreterType* pit, /*out*/UINT* pFldSize, /*out*/bool* pManagedMem)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
bool isCacheable = true;
*pManagedMem = true; // Default result.
unsigned tok = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));
m_ILCodePtr += 5; // Above is last use of m_ILCodePtr in this method, so update now.
FieldDesc* fld;
CORINFO_FIELD_INFO fldInfo;
CORINFO_RESOLVED_TOKEN fldTok;
void* pFldAddr = NULL;
{
{
GCX_PREEMP();
ResolveToken(&fldTok, tok, CORINFO_TOKENKIND_Field InterpTracingArg(RTK_SFldAddr));
fld = reinterpret_cast<FieldDesc*>(fldTok.hField);
m_interpCeeInfo.getFieldInfo(&fldTok, m_methInfo->m_method, accessFlgs, &fldInfo);
}
EnsureClassInit(GetMethodTableFromClsHnd(fldTok.hClass));
if ((fldInfo.fieldAccessor == CORINFO_FIELD_STATIC_TLS) || (fldInfo.fieldAccessor == CORINFO_FIELD_STATIC_TLS_MANAGED))
{
NYI_INTERP("Thread-local static.");
}
else if (fldInfo.fieldAccessor == CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER
|| fldInfo.fieldAccessor == CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER)
{
*pStaticFieldAddr = fld->GetCurrentStaticAddress();
isCacheable = false;
}
else
{
*pStaticFieldAddr = fld->GetCurrentStaticAddress();
}
}
if (fldInfo.structType != NULL && fldInfo.fieldType != CORINFO_TYPE_CLASS && fldInfo.fieldType != CORINFO_TYPE_PTR)
{
*pit = InterpreterType(&m_interpCeeInfo, fldInfo.structType);
if ((fldInfo.fieldFlags & CORINFO_FLG_FIELD_UNMANAGED) == 0)
{
// For valuetypes in managed memory, the address returned contains a pointer into the heap, to a boxed version of the
// static variable; return a pointer to the boxed struct.
isCacheable = false;
}
else
{
*pManagedMem = false;
}
}
else
{
*pit = InterpreterType(fldInfo.fieldType);
}
*pFldSize = fld->GetSize();
return isCacheable;
}
void Interpreter::LdSFld()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
InterpreterType fldIt;
UINT sz;
bool managedMem;
void* srcPtr = NULL;
BarrierIfVolatile();
GCPROTECT_BEGININTERIOR(srcPtr);
StaticFldAddr(CORINFO_ACCESS_GET, &srcPtr, &fldIt, &sz, &managedMem);
bool isUnsigned;
if (fldIt.IsStruct())
{
// Large struct case.
CORINFO_CLASS_HANDLE sh = fldIt.ToClassHandle();
// This call is GC_TRIGGERS, so do it before we copy the value: no GC after this,
// until the op stacks and ht are consistent.
OpStackTypeSet(m_curStackHt, InterpreterType(&m_interpCeeInfo, sh).StackNormalize());
if (fldIt.IsLargeStruct(&m_interpCeeInfo))
{
void* dstPtr = LargeStructOperandStackPush(sz);
memcpy(dstPtr, srcPtr, sz);
OpStackSet<void*>(m_curStackHt, dstPtr);
}
else
{
OpStackSet<INT64>(m_curStackHt, GetSmallStructValue(srcPtr, sz));
}
}
else
{
CorInfoType valCit = fldIt.ToCorInfoType();
switch (sz)
{
case 1:
isUnsigned = CorInfoTypeIsUnsigned(valCit);
if (isUnsigned)
{
OpStackSet<UINT32>(m_curStackHt, *reinterpret_cast<UINT8*>(srcPtr));
}
else
{
OpStackSet<INT32>(m_curStackHt, *reinterpret_cast<INT8*>(srcPtr));
}
break;
case 2:
isUnsigned = CorInfoTypeIsUnsigned(valCit);
if (isUnsigned)
{
OpStackSet<UINT32>(m_curStackHt, *reinterpret_cast<UINT16*>(srcPtr));
}
else
{
OpStackSet<INT32>(m_curStackHt, *reinterpret_cast<INT16*>(srcPtr));
}
break;
case 4:
OpStackSet<INT32>(m_curStackHt, *reinterpret_cast<INT32*>(srcPtr));
break;
case 8:
OpStackSet<INT64>(m_curStackHt, *reinterpret_cast<INT64*>(srcPtr));
break;
default:
_ASSERTE_MSG(false, "LdSFld: this should have exhausted all the possible sizes.");
break;
}
OpStackTypeSet(m_curStackHt, fldIt.StackNormalize());
}
m_curStackHt++;
GCPROTECT_END();
}
void Interpreter::EnsureClassInit(MethodTable* pMT)
{
if (!pMT->IsClassInited())
{
pMT->CheckRestore();
// This is tantamount to a call, so exempt it from the cycle count.
#if INTERP_ILCYCLE_PROFILE
unsigned __int64 startCycles;
bool b = CycleTimer::GetThreadCyclesS(&startCycles); _ASSERTE(b);
#endif // INTERP_ILCYCLE_PROFILE
pMT->CheckRunClassInitThrowing();
#if INTERP_ILCYCLE_PROFILE
unsigned __int64 endCycles;
b = CycleTimer::GetThreadCyclesS(&endCycles); _ASSERTE(b);
m_exemptCycles += (endCycles - startCycles);
#endif // INTERP_ILCYCLE_PROFILE
}
}
void Interpreter::LdSFldA()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
InterpreterType fldIt;
UINT fldSz;
bool managedMem;
void* srcPtr = NULL;
GCPROTECT_BEGININTERIOR(srcPtr);
StaticFldAddr(CORINFO_ACCESS_ADDRESS, &srcPtr, &fldIt, &fldSz, &managedMem);
OpStackSet<void*>(m_curStackHt, srcPtr);
if (managedMem)
{
// Static variable in managed memory...
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_BYREF));
}
else
{
// RVA is in unmanaged memory.
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_NATIVEINT));
}
m_curStackHt++;
GCPROTECT_END();
}
void Interpreter::StSFld()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
InterpreterType fldIt;
UINT sz;
bool managedMem;
void* dstPtr = NULL;
GCPROTECT_BEGININTERIOR(dstPtr);
StaticFldAddr(CORINFO_ACCESS_SET, &dstPtr, &fldIt, &sz, &managedMem);
m_curStackHt--;
InterpreterType valIt = OpStackTypeGet(m_curStackHt);
CorInfoType valCit = valIt.ToCorInfoType();
if (valCit == CORINFO_TYPE_VALUECLASS)
{
MethodTable* valClsMT = GetMethodTableFromClsHnd(valIt.ToClassHandle());
if (sz > sizeof(INT64))
{
// Large struct case: value in operand stack is indirect pointer.
void* srcPtr = OpStackGet<void*>(m_curStackHt);
CopyValueClassUnchecked(dstPtr, srcPtr, valClsMT);
LargeStructOperandStackPop(sz, srcPtr);
}
else
{
// Struct value is inline in the operand stack.
CopyValueClassUnchecked(dstPtr, OpStackGetAddr(m_curStackHt, sz), valClsMT);
}
}
else if (valCit == CORINFO_TYPE_CLASS)
{
SetObjectReference(reinterpret_cast<OBJECTREF*>(dstPtr), ObjectToOBJECTREF(OpStackGet<Object*>(m_curStackHt)));
}
else
{
switch (sz)
{
case 1:
*reinterpret_cast<UINT8*>(dstPtr) = OpStackGet<UINT8>(m_curStackHt);
break;
case 2:
*reinterpret_cast<UINT16*>(dstPtr) = OpStackGet<UINT16>(m_curStackHt);
break;
case 4:
*reinterpret_cast<UINT32*>(dstPtr) = OpStackGet<UINT32>(m_curStackHt);
break;
case 8:
*reinterpret_cast<UINT64*>(dstPtr) = OpStackGet<UINT64>(m_curStackHt);
break;
default:
_ASSERTE_MSG(false, "This should have exhausted all the possible sizes.");
break;
}
}
GCPROTECT_END();
BarrierIfVolatile();
}
template<typename T, bool IsObjType, CorInfoType cit>
void Interpreter::LdElemWithType()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 2);
unsigned arrInd = m_curStackHt - 2;
unsigned indexInd = m_curStackHt - 1;
_ASSERTE(OpStackTypeGet(arrInd).ToCorInfoType() == CORINFO_TYPE_CLASS);
ArrayBase* a = OpStackGet<ArrayBase*>(arrInd);
ThrowOnInvalidPointer(a);
int len = a->GetNumComponents();
CorInfoType indexCit = OpStackTypeGet(indexInd).ToCorInfoType();
if (indexCit == CORINFO_TYPE_INT)
{
int index = OpStackGet<INT32>(indexInd);
if (index < 0 || index >= len) ThrowArrayBoundsException();
GCX_FORBID();
if (IsObjType)
{
OBJECTREF res = reinterpret_cast<PtrArray*>(a)->GetAt(index);
OpStackSet<OBJECTREF>(arrInd, res);
}
else
{
intptr_t res_ptr = reinterpret_cast<intptr_t>(reinterpret_cast<Array<T>*>(a)->GetDirectConstPointerToNonObjectElements());
if (cit == CORINFO_TYPE_INT)
{
_ASSERTE(std::is_integral<T>::value);
// Widen narrow types.
int ires;
switch (sizeof(T))
{
case 1:
ires = std::is_same<T, INT8>::value ?
static_cast<int>(reinterpret_cast<INT8*>(res_ptr)[index]) :
static_cast<int>(reinterpret_cast<UINT8*>(res_ptr)[index]);
break;
case 2:
ires = std::is_same<T, INT16>::value ?
static_cast<int>(reinterpret_cast<INT16*>(res_ptr)[index]) :
static_cast<int>(reinterpret_cast<UINT16*>(res_ptr)[index]);
break;
case 4:
ires = std::is_same<T, INT32>::value ?
static_cast<int>(reinterpret_cast<INT32*>(res_ptr)[index]) :
static_cast<int>(reinterpret_cast<UINT32*>(res_ptr)[index]);
break;
default:
_ASSERTE_MSG(false, "This should have exhausted all the possible sizes.");
break;
}
OpStackSet<int>(arrInd, ires);
}
else
{
OpStackSet<T>(arrInd, ((T*) res_ptr)[index]);
}
}
}
else
{
_ASSERTE(indexCit == CORINFO_TYPE_NATIVEINT);
NativeInt index = OpStackGet<NativeInt>(indexInd);
if (index < 0 || index >= NativeInt(len)) ThrowArrayBoundsException();
GCX_FORBID();
if (IsObjType)
{
OBJECTREF res = reinterpret_cast<PtrArray*>(a)->GetAt(index);
OpStackSet<OBJECTREF>(arrInd, res);
}
else
{
T res = reinterpret_cast<Array<T>*>(a)->GetDirectConstPointerToNonObjectElements()[index];
OpStackSet<T>(arrInd, res);
}
}
OpStackTypeSet(arrInd, InterpreterType(cit));
m_curStackHt--;
}
template<typename T, bool IsObjType>
void Interpreter::StElemWithType()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 3);
unsigned arrInd = m_curStackHt - 3;
unsigned indexInd = m_curStackHt - 2;
unsigned valInd = m_curStackHt - 1;
_ASSERTE(OpStackTypeGet(arrInd).ToCorInfoType() == CORINFO_TYPE_CLASS);
ArrayBase* a = OpStackGet<ArrayBase*>(arrInd);
ThrowOnInvalidPointer(a);
int len = a->GetNumComponents();
CorInfoType indexCit = OpStackTypeGet(indexInd).ToCorInfoType();
if (indexCit == CORINFO_TYPE_INT)
{
int index = OpStackGet<INT32>(indexInd);
if (index < 0 || index >= len) ThrowArrayBoundsException();
if (IsObjType)
{
struct _gc {
OBJECTREF val;
OBJECTREF a;
} gc;
gc.val = ObjectToOBJECTREF(OpStackGet<Object*>(valInd));
gc.a = ObjectToOBJECTREF(a);
GCPROTECT_BEGIN(gc);
if (gc.val != NULL &&
!ObjIsInstanceOf(OBJECTREFToObject(gc.val), reinterpret_cast<PtrArray*>(a)->GetArrayElementTypeHandle()))
COMPlusThrow(kArrayTypeMismatchException);
reinterpret_cast<PtrArray*>(OBJECTREFToObject(gc.a))->SetAt(index, gc.val);
GCPROTECT_END();
}
else
{
GCX_FORBID();
T val = OpStackGet<T>(valInd);
reinterpret_cast<Array<T>*>(a)->GetDirectPointerToNonObjectElements()[index] = val;
}
}
else
{
_ASSERTE(indexCit == CORINFO_TYPE_NATIVEINT);
NativeInt index = OpStackGet<NativeInt>(indexInd);
if (index < 0 || index >= NativeInt(len)) ThrowArrayBoundsException();
if (IsObjType)
{
struct _gc {
OBJECTREF val;
OBJECTREF a;
} gc;
gc.val = ObjectToOBJECTREF(OpStackGet<Object*>(valInd));
gc.a = ObjectToOBJECTREF(a);
GCPROTECT_BEGIN(gc);
if (gc.val != NULL &&
!ObjIsInstanceOf(OBJECTREFToObject(gc.val), reinterpret_cast<PtrArray*>(a)->GetArrayElementTypeHandle()))
COMPlusThrow(kArrayTypeMismatchException);
reinterpret_cast<PtrArray*>(OBJECTREFToObject(gc.a))->SetAt(index, gc.val);
GCPROTECT_END();
}
else
{
GCX_FORBID();
T val = OpStackGet<T>(valInd);
reinterpret_cast<Array<T>*>(a)->GetDirectPointerToNonObjectElements()[index] = val;
}
}
m_curStackHt -= 3;
}
template<bool takeAddress>
void Interpreter::LdElem()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 2);
unsigned arrInd = m_curStackHt - 2;
unsigned indexInd = m_curStackHt - 1;
unsigned elemTypeTok = getU4LittleEndian(m_ILCodePtr + 1);
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdElem]);
#endif // INTERP_TRACING
unsigned ilOffset = CurOffset();
CORINFO_CLASS_HANDLE clsHnd = NULL;
if (s_InterpreterUseCaching) clsHnd = GetCachedClassHandle(ilOffset);
if (clsHnd == NULL)
{
CORINFO_RESOLVED_TOKEN elemTypeResolvedTok;
{
GCX_PREEMP();
ResolveToken(&elemTypeResolvedTok, elemTypeTok, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_LdElem));
clsHnd = elemTypeResolvedTok.hClass;
}
if (s_InterpreterUseCaching) CacheClassHandle(ilOffset, clsHnd);
}
CorInfoType elemCit = ::asCorInfoType(clsHnd);
m_ILCodePtr += 5;
InterpreterType elemIt;
if (elemCit == CORINFO_TYPE_VALUECLASS)
{
elemIt = InterpreterType(&m_interpCeeInfo, clsHnd);
}
else
{
elemIt = InterpreterType(elemCit);
}
_ASSERTE(OpStackTypeGet(arrInd).ToCorInfoType() == CORINFO_TYPE_CLASS);
ArrayBase* a = OpStackGet<ArrayBase*>(arrInd);
ThrowOnInvalidPointer(a);
int len = a->GetNumComponents();
NativeInt index;
{
GCX_FORBID();
CorInfoType indexCit = OpStackTypeGet(indexInd).ToCorInfoType();
if (indexCit == CORINFO_TYPE_INT)
{
index = static_cast<NativeInt>(OpStackGet<INT32>(indexInd));
}
else
{
_ASSERTE(indexCit == CORINFO_TYPE_NATIVEINT);
index = OpStackGet<NativeInt>(indexInd);
}
}
if (index < 0 || index >= len) ThrowArrayBoundsException();
bool throwTypeMismatch = NULL;
{
void* elemPtr = a->GetDataPtr() + a->GetComponentSize() * index;
// elemPtr is now a vulnerable byref.
GCX_FORBID();
if (takeAddress)
{
// If the element type is a class type, may have to do a type check.
if (elemCit == CORINFO_TYPE_CLASS)
{
// Unless there was a readonly prefix, which removes the need to
// do the (dynamic) type check.
if (m_readonlyFlag)
{
// Consume the readonly prefix, and don't do the type check below.
m_readonlyFlag = false;
}
else
{
PtrArray* pa = reinterpret_cast<PtrArray*>(a);
// The element array type must be exactly the referent type of the managed
// pointer we'll be creating.
if (pa->GetArrayElementTypeHandle() != TypeHandle(clsHnd))
{
throwTypeMismatch = true;
}
}
}
if (!throwTypeMismatch)
{
// If we're not going to throw the exception, we can take the address.
OpStackSet<void*>(arrInd, elemPtr);
OpStackTypeSet(arrInd, InterpreterType(CORINFO_TYPE_BYREF));
m_curStackHt--;
}
}
else
{
m_curStackHt -= 2;
LdFromMemAddr(elemPtr, elemIt);
return;
}
}
// If we're going to throw, we do the throw outside the GCX_FORBID region above, since it requires GC_TRIGGERS.
if (throwTypeMismatch)
{
COMPlusThrow(kArrayTypeMismatchException);
}
}
void Interpreter::StElem()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 3);
unsigned arrInd = m_curStackHt - 3;
unsigned indexInd = m_curStackHt - 2;
unsigned valInd = m_curStackHt - 1;
CorInfoType valCit = OpStackTypeGet(valInd).ToCorInfoType();
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_StElem]);
#endif // INTERP_TRACING
CORINFO_CLASS_HANDLE typeFromTok = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_StElem));
m_ILCodePtr += 5;
CorInfoType typeFromTokCit;
{
GCX_PREEMP();
typeFromTokCit = ::asCorInfoType(typeFromTok);
}
size_t sz;
#ifdef _DEBUG
InterpreterType typeFromTokIt;
#endif // _DEBUG
if (typeFromTokCit == CORINFO_TYPE_VALUECLASS)
{
GCX_PREEMP();
sz = getClassSize(typeFromTok);
#ifdef _DEBUG
typeFromTokIt = InterpreterType(&m_interpCeeInfo, typeFromTok);
#endif // _DEBUG
}
else
{
sz = CorInfoTypeSize(typeFromTokCit);
#ifdef _DEBUG
typeFromTokIt = InterpreterType(typeFromTokCit);
#endif // _DEBUG
}
#ifdef _DEBUG
// Instead of debug, I need to parameterize the interpreter at the top level over whether
// to do checks corresponding to verification.
if (typeFromTokIt.StackNormalize().ToCorInfoType() != valCit)
{
// This is obviously only a partial test of the required condition.
VerificationError("Value in stelem does not have the required type.");
}
#endif // _DEBUG
_ASSERTE(OpStackTypeGet(arrInd).ToCorInfoType() == CORINFO_TYPE_CLASS);
ArrayBase* a = OpStackGet<ArrayBase*>(arrInd);
ThrowOnInvalidPointer(a);
int len = a->GetNumComponents();
CorInfoType indexCit = OpStackTypeGet(indexInd).ToCorInfoType();
NativeInt index = 0;
if (indexCit == CORINFO_TYPE_INT)
{
index = static_cast<NativeInt>(OpStackGet<INT32>(indexInd));
}
else
{
index = OpStackGet<NativeInt>(indexInd);
}
if (index < 0 || index >= len) ThrowArrayBoundsException();
if (typeFromTokCit == CORINFO_TYPE_CLASS)
{
struct _gc {
OBJECTREF val;
OBJECTREF a;
} gc;
gc.val = ObjectToOBJECTREF(OpStackGet<Object*>(valInd));
gc.a = ObjectToOBJECTREF(a);
GCPROTECT_BEGIN(gc);
if (gc.val != NULL &&
!ObjIsInstanceOf(OBJECTREFToObject(gc.val), reinterpret_cast<PtrArray*>(a)->GetArrayElementTypeHandle()))
COMPlusThrow(kArrayTypeMismatchException);
reinterpret_cast<PtrArray*>(OBJECTREFToObject(gc.a))->SetAt(index, gc.val);
GCPROTECT_END();
}
else
{
GCX_FORBID();
void* destPtr = a->GetDataPtr() + index * sz;;
if (typeFromTokCit == CORINFO_TYPE_VALUECLASS)
{
MethodTable* valClsMT = GetMethodTableFromClsHnd(OpStackTypeGet(valInd).ToClassHandle());
// I use GCSafeMemCpy below to ensure that write barriers happen for the case in which
// the value class contains GC pointers. We could do better...
if (sz > sizeof(UINT64))
{
// Large struct case: stack slot contains pointer...
void* src = OpStackGet<void*>(valInd);
CopyValueClassUnchecked(destPtr, src, valClsMT);
LargeStructOperandStackPop(sz, src);
}
else
{
// Small struct case -- is inline in operand stack.
CopyValueClassUnchecked(destPtr, OpStackGetAddr(valInd, sz), valClsMT);
}
}
else
{
switch (sz)
{
case 1:
*reinterpret_cast<INT8*>(destPtr) = OpStackGet<INT8>(valInd);
break;
case 2:
*reinterpret_cast<INT16*>(destPtr) = OpStackGet<INT16>(valInd);
break;
case 4:
*reinterpret_cast<INT32*>(destPtr) = OpStackGet<INT32>(valInd);
break;
case 8:
*reinterpret_cast<INT64*>(destPtr) = OpStackGet<INT64>(valInd);
break;
}
}
}
m_curStackHt -= 3;
}
void Interpreter::InitBlk()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 3);
unsigned addrInd = m_curStackHt - 3;
unsigned valInd = m_curStackHt - 2;
unsigned sizeInd = m_curStackHt - 1;
#ifdef _DEBUG
CorInfoType addrCIT = OpStackTypeGet(addrInd).ToCorInfoType();
bool addrValidType = (addrCIT == CORINFO_TYPE_NATIVEINT || addrCIT == CORINFO_TYPE_BYREF);
#if defined(HOST_AMD64)
if (s_InterpreterLooseRules && addrCIT == CORINFO_TYPE_LONG)
addrValidType = true;
#endif
if (!addrValidType)
VerificationError("Addr of InitBlk must be native int or &.");
CorInfoType valCIT = OpStackTypeGet(valInd).ToCorInfoType();
if (valCIT != CORINFO_TYPE_INT)
VerificationError("Value of InitBlk must be int");
#endif // _DEBUG
CorInfoType sizeCIT = OpStackTypeGet(sizeInd).ToCorInfoType();
bool isLong = s_InterpreterLooseRules && (sizeCIT == CORINFO_TYPE_LONG);
#ifdef _DEBUG
if (sizeCIT != CORINFO_TYPE_INT && !isLong)
VerificationError("Size of InitBlk must be int");
#endif // _DEBUG
void* addr = OpStackGet<void*>(addrInd);
ThrowOnInvalidPointer(addr);
GCX_FORBID(); // addr is a potentially vulnerable byref.
INT8 val = OpStackGet<INT8>(valInd);
size_t size = (size_t) ((isLong) ? OpStackGet<UINT64>(sizeInd) : OpStackGet<UINT32>(sizeInd));
memset(addr, val, size);
m_curStackHt = addrInd;
m_ILCodePtr += 2;
BarrierIfVolatile();
}
void Interpreter::CpBlk()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 3);
unsigned destInd = m_curStackHt - 3;
unsigned srcInd = m_curStackHt - 2;
unsigned sizeInd = m_curStackHt - 1;
#ifdef _DEBUG
CorInfoType destCIT = OpStackTypeGet(destInd).ToCorInfoType();
bool destValidType = (destCIT == CORINFO_TYPE_NATIVEINT || destCIT == CORINFO_TYPE_BYREF);
#if defined(HOST_AMD64)
if (s_InterpreterLooseRules && destCIT == CORINFO_TYPE_LONG)
destValidType = true;
#endif
if (!destValidType)
{
VerificationError("Dest addr of CpBlk must be native int or &.");
}
CorInfoType srcCIT = OpStackTypeGet(srcInd).ToCorInfoType();
bool srcValidType = (srcCIT == CORINFO_TYPE_NATIVEINT || srcCIT == CORINFO_TYPE_BYREF);
#if defined(HOST_AMD64)
if (s_InterpreterLooseRules && srcCIT == CORINFO_TYPE_LONG)
srcValidType = true;
#endif
if (!srcValidType)
VerificationError("Src addr of CpBlk must be native int or &.");
#endif // _DEBUG
CorInfoType sizeCIT = OpStackTypeGet(sizeInd).ToCorInfoType();
bool isLong = s_InterpreterLooseRules && (sizeCIT == CORINFO_TYPE_LONG);
#ifdef _DEBUG
if (sizeCIT != CORINFO_TYPE_INT && !isLong)
VerificationError("Size of CpBlk must be int");
#endif // _DEBUG
void* destAddr = OpStackGet<void*>(destInd);
void* srcAddr = OpStackGet<void*>(srcInd);
ThrowOnInvalidPointer(destAddr);
ThrowOnInvalidPointer(srcAddr);
GCX_FORBID(); // destAddr & srcAddr are potentially vulnerable byrefs.
size_t size = (size_t)((isLong) ? OpStackGet<UINT64>(sizeInd) : OpStackGet<UINT32>(sizeInd));
memcpyNoGCRefs(destAddr, srcAddr, size);
m_curStackHt = destInd;
m_ILCodePtr += 2;
BarrierIfVolatile();
}
void Interpreter::Box()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 1);
unsigned ind = m_curStackHt - 1;
DWORD boxTypeAttribs = 0;
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_Box]);
#endif // INTERP_TRACING
CORINFO_CLASS_HANDLE boxTypeClsHnd = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_Box));
{
GCX_PREEMP();
boxTypeAttribs = m_interpCeeInfo.getClassAttribs(boxTypeClsHnd);
}
m_ILCodePtr += 5;
if (boxTypeAttribs & CORINFO_FLG_VALUECLASS)
{
InterpreterType valIt = OpStackTypeGet(ind);
void* valPtr;
if (valIt.IsLargeStruct(&m_interpCeeInfo))
{
// Operand stack entry is pointer to the data.
valPtr = OpStackGet<void*>(ind);
}
else
{
// Operand stack entry *is* the data.
size_t classSize = getClassSize(boxTypeClsHnd);
valPtr = OpStackGetAddr(ind, classSize);
}
TypeHandle th(boxTypeClsHnd);
if (th.IsTypeDesc())
{
COMPlusThrow(kInvalidOperationException, W("InvalidOperation_TypeCannotBeBoxed"));
}
MethodTable* pMT = th.AsMethodTable();
{
Object* res = OBJECTREFToObject(pMT->Box(valPtr));
GCX_FORBID();
// If we're popping a large struct off the operand stack, make sure we clean up.
if (valIt.IsLargeStruct(&m_interpCeeInfo))
{
LargeStructOperandStackPop(valIt.Size(&m_interpCeeInfo), valPtr);
}
OpStackSet<Object*>(ind, res);
OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_CLASS));
}
}
}
void Interpreter::BoxStructRefAt(unsigned ind, CORINFO_CLASS_HANDLE valCls)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE_MSG(ind < m_curStackHt, "Precondition");
{
GCX_PREEMP();
_ASSERTE_MSG(m_interpCeeInfo.getClassAttribs(valCls) & CORINFO_FLG_VALUECLASS, "Precondition");
}
_ASSERTE_MSG(OpStackTypeGet(ind).ToCorInfoType() == CORINFO_TYPE_BYREF, "Precondition");
InterpreterType valIt = InterpreterType(&m_interpCeeInfo, valCls);
void* valPtr = OpStackGet<void*>(ind);
TypeHandle th(valCls);
if (th.IsTypeDesc())
COMPlusThrow(kInvalidOperationException,W("InvalidOperation_TypeCannotBeBoxed"));
MethodTable* pMT = th.AsMethodTable();
{
Object* res = OBJECTREFToObject(pMT->Box(valPtr));
GCX_FORBID();
OpStackSet<Object*>(ind, res);
OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_CLASS));
}
}
void Interpreter::Unbox()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END
_ASSERTE(m_curStackHt > 0);
unsigned tos = m_curStackHt - 1;
#ifdef _DEBUG
CorInfoType tosCIT = OpStackTypeGet(tos).ToCorInfoType();
if (tosCIT != CORINFO_TYPE_CLASS)
VerificationError("Unbox requires that TOS is an object pointer.");
#endif // _DEBUG
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_Unbox]);
#endif // INTERP_TRACING
CORINFO_CLASS_HANDLE boxTypeClsHnd = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_Unbox));
CorInfoHelpFunc unboxHelper;
{
GCX_PREEMP();
unboxHelper = m_interpCeeInfo.getUnBoxHelper(boxTypeClsHnd);
}
void* res = NULL;
Object* obj = OpStackGet<Object*>(tos);
switch (unboxHelper)
{
case CORINFO_HELP_UNBOX:
{
ThrowOnInvalidPointer(obj);
MethodTable* pMT1 = (MethodTable*)boxTypeClsHnd;
MethodTable* pMT2 = obj->GetMethodTable();
if (pMT1->IsEquivalentTo(pMT2))
{
res = OpStackGet<Object*>(tos)->UnBox();
}
else
{
CorElementType type1 = pMT1->GetInternalCorElementType();
CorElementType type2 = pMT2->GetInternalCorElementType();
// we allow enums and their primitive type to be interchangeable
if (type1 == type2)
{
if ((pMT1->IsEnum() || pMT1->IsTruePrimitive()) &&
(pMT2->IsEnum() || pMT2->IsTruePrimitive()))
{
res = OpStackGet<Object*>(tos)->UnBox();
}
}
}
if (res == NULL)
{
COMPlusThrow(kInvalidCastException);
}
}
break;
case CORINFO_HELP_UNBOX_NULLABLE:
{
// For "unbox Nullable<T>", we need to create a new object (maybe in some temporary local
// space (that we reuse every time we hit this IL instruction?), that gets reported to the GC,
// maybe in the GC heap itself). That object will contain an embedded Nullable<T>. Then, we need to
// get a byref to the data within the object.
NYI_INTERP("Unhandled 'unbox' of Nullable<T>.");
}
break;
default:
NYI_INTERP("Unhandled 'unbox' helper.");
}
{
GCX_FORBID();
OpStackSet<void*>(tos, res);
OpStackTypeSet(tos, InterpreterType(CORINFO_TYPE_BYREF));
}
m_ILCodePtr += 5;
}
void Interpreter::Throw()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END
_ASSERTE(m_curStackHt >= 1);
// Note that we can't decrement the stack height here, since the operand stack
// protects the thrown object. Nor do we need to, since the ostack will be cleared on
// any catch within this method.
unsigned exInd = m_curStackHt - 1;
#ifdef _DEBUG
CorInfoType exCIT = OpStackTypeGet(exInd).ToCorInfoType();
if (exCIT != CORINFO_TYPE_CLASS)
{
VerificationError("Can only throw an object.");
}
#endif // _DEBUG
Object* obj = OpStackGet<Object*>(exInd);
ThrowOnInvalidPointer(obj);
OBJECTREF oref = ObjectToOBJECTREF(obj);
if (!IsException(oref->GetMethodTable()))
{
GCPROTECT_BEGIN(oref);
WrapNonCompliantException(&oref);
GCPROTECT_END();
}
COMPlusThrow(oref);
}
void Interpreter::Rethrow()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END
OBJECTREF throwable = GetThread()->LastThrownObject();
COMPlusThrow(throwable);
}
void Interpreter::UnboxAny()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt > 0);
unsigned tos = m_curStackHt - 1;
unsigned boxTypeTok = getU4LittleEndian(m_ILCodePtr + 1);
m_ILCodePtr += 5;
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_UnboxAny]);
#endif // INTERP_TRACING
CORINFO_RESOLVED_TOKEN boxTypeResolvedTok;
CORINFO_CLASS_HANDLE boxTypeClsHnd;
DWORD boxTypeAttribs = 0;
{
GCX_PREEMP();
ResolveToken(&boxTypeResolvedTok, boxTypeTok, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_UnboxAny));
boxTypeClsHnd = boxTypeResolvedTok.hClass;
boxTypeAttribs = m_interpCeeInfo.getClassAttribs(boxTypeClsHnd);
}
CorInfoType unboxCIT = OpStackTypeGet(tos).ToCorInfoType();
if (unboxCIT != CORINFO_TYPE_CLASS)
VerificationError("Type mismatch in UNBOXANY.");
if ((boxTypeAttribs & CORINFO_FLG_VALUECLASS) == 0)
{
Object* obj = OpStackGet<Object*>(tos);
if (obj != NULL && !ObjIsInstanceOf(obj, TypeHandle(boxTypeClsHnd), TRUE))
{
UNREACHABLE(); //ObjIsInstanceOf will throw if cast can't be done
}
}
else
{
CorInfoHelpFunc unboxHelper;
{
GCX_PREEMP();
unboxHelper = m_interpCeeInfo.getUnBoxHelper(boxTypeClsHnd);
}
// Important that this *not* be factored out with the identical statement in the "if" branch:
// delay read from GC-protected operand stack until after COOP-->PREEMP transition above.
Object* obj = OpStackGet<Object*>(tos);
switch (unboxHelper)
{
case CORINFO_HELP_UNBOX:
{
ThrowOnInvalidPointer(obj);
MethodTable* pMT1 = (MethodTable*)boxTypeClsHnd;
MethodTable* pMT2 = obj->GetMethodTable();
void* res = NULL;
if (pMT1->IsEquivalentTo(pMT2))
{
res = OpStackGet<Object*>(tos)->UnBox();
}
else
{
if (pMT1->GetInternalCorElementType() == pMT2->GetInternalCorElementType() &&
(pMT1->IsEnum() || pMT1->IsTruePrimitive()) &&
(pMT2->IsEnum() || pMT2->IsTruePrimitive()))
{
res = OpStackGet<Object*>(tos)->UnBox();
}
}
if (res == NULL)
{
COMPlusThrow(kInvalidCastException);
}
// As the ECMA spec says, the rest is like a "ldobj".
LdObjValueClassWork(boxTypeClsHnd, tos, res);
}
break;
case CORINFO_HELP_UNBOX_NULLABLE:
{
InterpreterType it = InterpreterType(&m_interpCeeInfo, boxTypeClsHnd);
size_t sz = it.Size(&m_interpCeeInfo);
if (sz > sizeof(INT64))
{
void* destPtr = LargeStructOperandStackPush(sz);
if (!Nullable::UnBox(destPtr, ObjectToOBJECTREF(obj), (MethodTable*)boxTypeClsHnd))
{
COMPlusThrow(kInvalidCastException);
}
OpStackSet<void*>(tos, destPtr);
}
else
{
INT64 dest = 0;
if (!Nullable::UnBox(&dest, ObjectToOBJECTREF(obj), (MethodTable*)boxTypeClsHnd))
{
COMPlusThrow(kInvalidCastException);
}
OpStackSet<INT64>(tos, dest);
}
OpStackTypeSet(tos, it.StackNormalize());
}
break;
default:
NYI_INTERP("Unhandled 'unbox.any' helper.");
}
}
}
void Interpreter::LdLen()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 1);
unsigned arrInd = m_curStackHt - 1;
_ASSERTE(OpStackTypeGet(arrInd).ToCorInfoType() == CORINFO_TYPE_CLASS);
GCX_FORBID();
ArrayBase* a = OpStackGet<ArrayBase*>(arrInd);
ThrowOnInvalidPointer(a);
int len = a->GetNumComponents();
OpStackSet<NativeUInt>(arrInd, NativeUInt(len));
// The ECMA spec says that the type of the length value is NATIVEUINT, but this
// doesn't make any sense -- unsigned types are not stack-normalized. So I'm
// using NATIVEINT, to get the width right.
OpStackTypeSet(arrInd, InterpreterType(CORINFO_TYPE_NATIVEINT));
}
void Interpreter::DoCall(bool virtualCall)
{
#if INTERP_DYNAMIC_CONTRACTS
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#else
// Dynamic contract occupies too much stack.
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_COOPERATIVE;
#endif
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_Call]);
#endif // INTERP_TRACING
DoCallWork(virtualCall);
m_ILCodePtr += 5;
}
CORINFO_CONTEXT_HANDLE InterpreterMethodInfo::GetPreciseGenericsContext(Object* thisArg, void* genericsCtxtArg)
{
// If the caller has a generic argument, then we need to get the exact methodContext.
// There are several possibilities that lead to a generic argument:
// 1) Static method of generic class: generic argument is the method table of the class.
// 2) generic method of a class: generic argument is the precise MethodDesc* of the method.
if (GetFlag<InterpreterMethodInfo::Flag_hasGenericsContextArg>())
{
_ASSERTE(GetFlag<InterpreterMethodInfo::Flag_methHasGenericArgs>() || GetFlag<InterpreterMethodInfo::Flag_typeHasGenericArgs>());
if (GetFlag<InterpreterMethodInfo::Flag_methHasGenericArgs>())
{
return MAKE_METHODCONTEXT(reinterpret_cast<CORINFO_METHOD_HANDLE>(genericsCtxtArg));
}
else
{
MethodTable* methodClass = reinterpret_cast<MethodDesc*>(m_method)->GetMethodTable();
MethodTable* contextClass = reinterpret_cast<MethodTable*>(genericsCtxtArg)->GetMethodTableMatchingParentClass(methodClass);
return MAKE_CLASSCONTEXT(contextClass);
}
}
// TODO: This condition isn't quite right. If the actual class is a subtype of the declaring type of the method,
// then it might be in another module, the scope and context won't agree.
else if (GetFlag<InterpreterMethodInfo::Flag_typeHasGenericArgs>()
&& !GetFlag<InterpreterMethodInfo::Flag_methHasGenericArgs>()
&& GetFlag<InterpreterMethodInfo::Flag_hasThisArg>()
&& GetFlag<InterpreterMethodInfo::Flag_thisArgIsObjPtr>() && thisArg != NULL)
{
MethodTable* methodClass = reinterpret_cast<MethodDesc*>(m_method)->GetMethodTable();
MethodTable* contextClass = thisArg->GetMethodTable()->GetMethodTableMatchingParentClass(methodClass);
return MAKE_CLASSCONTEXT(contextClass);
}
else
{
return MAKE_METHODCONTEXT(m_method);
}
}
void Interpreter::DoCallWork(bool virtualCall, void* thisArg, CORINFO_RESOLVED_TOKEN* methTokPtr, CORINFO_CALL_INFO* callInfoPtr)
{
#if INTERP_DYNAMIC_CONTRACTS
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#else
// Dynamic contract occupies too much stack.
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_COOPERATIVE;
#endif
#if INTERP_ILCYCLE_PROFILE
#if 0
// XXX
unsigned __int64 callStartCycles;
bool b = CycleTimer::GetThreadCyclesS(&callStartCycles); _ASSERTE(b);
unsigned __int64 callStartExemptCycles = m_exemptCycles;
#endif
#endif // INTERP_ILCYCLE_PROFILE
#if INTERP_TRACING
InterlockedIncrement(&s_totalInterpCalls);
#endif // INTERP_TRACING
unsigned tok = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));
// It's possible for an IL method to push a capital-F Frame. If so, we pop it and save it;
// we'll push it back on after our GCPROTECT frame is popped.
Frame* ilPushedFrame = NULL;
// We can't protect "thisArg" with a GCPROTECT, because this pushes a Frame, and there
// exist managed methods that push (and pop) Frames -- so that the Frame chain does not return
// to its original state after a call. Therefore, we can't have a Frame on the stack over the duration
// of a call. (I assume that any method that calls a Frame-pushing IL method performs a matching
// call to pop that Frame before the caller method completes. If this were not true, if one method could push
// a Frame, but defer the pop to its caller, then we could *never* use a Frame in the interpreter, and
// our implementation plan would be doomed.)
_ASSERTE(m_callThisArg == NULL);
m_callThisArg = thisArg;
// Have we already cached a MethodDescCallSite for this call? (We do this only in loops
// in the current execution).
unsigned iloffset = CurOffset();
CallSiteCacheData* pCscd = NULL;
if (s_InterpreterUseCaching) pCscd = GetCachedCallInfo(iloffset);
// If this is true, then we should not cache this call site.
bool doNotCache;
CORINFO_RESOLVED_TOKEN methTok;
CORINFO_CALL_INFO callInfo;
MethodDesc* methToCall = NULL;
CORINFO_CLASS_HANDLE exactClass = NULL;
CORINFO_SIG_INFO_SMALL sigInfo;
if (pCscd != NULL)
{
GCX_PREEMP();
methToCall = pCscd->m_pMD;
sigInfo = pCscd->m_sigInfo;
doNotCache = true; // We already have a cache entry.
}
else
{
doNotCache = false; // Until we determine otherwise.
if (callInfoPtr == NULL)
{
GCX_PREEMP();
// callInfoPtr and methTokPtr must either both be NULL, or neither.
_ASSERTE(methTokPtr == NULL);
methTokPtr = &methTok;
ResolveToken(methTokPtr, tok, CORINFO_TOKENKIND_Method InterpTracingArg(RTK_Call));
OPCODE opcode = (OPCODE)(*m_ILCodePtr);
m_interpCeeInfo.getCallInfo(methTokPtr,
m_constrainedFlag ? & m_constrainedResolvedToken : NULL,
m_methInfo->m_method,
//this is how impImportCall invokes getCallInfo
combine(combine(CORINFO_CALLINFO_ALLOWINSTPARAM,
CORINFO_CALLINFO_SECURITYCHECKS),
(opcode == CEE_CALLVIRT) ? CORINFO_CALLINFO_CALLVIRT
: CORINFO_CALLINFO_NONE),
&callInfo);
#if INTERP_ILCYCLE_PROFILE
#if 0
if (virtualCall)
{
unsigned __int64 callEndCycles;
b = CycleTimer::GetThreadCyclesS(&callEndCycles); _ASSERTE(b);
unsigned __int64 delta = (callEndCycles - callStartCycles);
delta -= (m_exemptCycles - callStartExemptCycles);
s_callCycles += delta;
s_calls++;
}
#endif
#endif // INTERP_ILCYCLE_PROFILE
callInfoPtr = &callInfo;
_ASSERTE(!callInfoPtr->exactContextNeedsRuntimeLookup);
methToCall = reinterpret_cast<MethodDesc*>(methTok.hMethod);
exactClass = methTok.hClass;
}
else
{
// callInfoPtr and methTokPtr must either both be NULL, or neither.
_ASSERTE(methTokPtr != NULL);
_ASSERTE(!callInfoPtr->exactContextNeedsRuntimeLookup);
methToCall = reinterpret_cast<MethodDesc*>(callInfoPtr->hMethod);
exactClass = methTokPtr->hClass;
}
// We used to take the sigInfo from the callInfo here, but that isn't precise, since
// we may have made "methToCall" more precise wrt generics than the method handle in
// the callinfo. So look up th emore precise signature.
GCX_PREEMP();
CORINFO_SIG_INFO sigInfoFull;
m_interpCeeInfo.getMethodSig(CORINFO_METHOD_HANDLE(methToCall), &sigInfoFull, nullptr);
sigInfo.retTypeClass = sigInfoFull.retTypeClass;
sigInfo.numArgs = sigInfoFull.numArgs;
sigInfo.callConv = sigInfoFull.callConv;
sigInfo.retType = sigInfoFull.retType;
sigInfo.fHasThis = CORINFO_SIG_INFO_SMALL::ComputeHasThis(methToCall);
}
// Point A in our cycle count.
// Is the method an intrinsic? If so, and if it's one we've written special-case code for
// handle intrinsically.
InterpreterNamedIntrinsics intrinsicId;
{
GCX_PREEMP();
intrinsicId = getNamedIntrinsicID(&m_interpCeeInfo, CORINFO_METHOD_HANDLE(methToCall));
}
#if INTERP_TRACING
if (intrinsicId == NI_Illegal)
InterlockedIncrement(&s_totalInterpCallsToIntrinsics);
#endif // INTERP_TRACING
bool didIntrinsic = false;
if (!m_constrainedFlag)
{
switch (intrinsicId)
{
case NI_System_Runtime_InteropService_MemoryMarshal_GetArrayDataReference:
DoGetArrayDataReference();
didIntrinsic = true;
break;
#if INTERP_ILSTUBS
case NI_System_StubHelpers_GetStubContext:
OpStackSet<void*>(m_curStackHt, GetStubContext());
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_NATIVEINT));
m_curStackHt++; didIntrinsic = true;
break;
#endif // INTERP_ILSTUBS
default:
#if INTERP_TRACING
InterlockedIncrement(&s_totalInterpCallsToIntrinsicsUnhandled);
#endif // INTERP_TRACING
break;
}
// TODO: The following check for hardware intrinsics is not a production-level
// solution and may produce incorrect results.
static ConfigDWORD s_InterpreterHWIntrinsicsIsSupportedFalse;
if (s_InterpreterHWIntrinsicsIsSupportedFalse.val(CLRConfig::INTERNAL_InterpreterHWIntrinsicsIsSupportedFalse) != 0)
{
GCX_PREEMP();
// Hardware intrinsics are recognized by name.
const char* namespaceName = NULL;
const char* className = NULL;
const char* methodName = getMethodName(&m_interpCeeInfo, (CORINFO_METHOD_HANDLE)methToCall, &className, &namespaceName, NULL);
if (
(strcmp(namespaceName, "System.Runtime.Intrinsics") == 0 ||
#if defined(TARGET_X86) || defined(TARGET_AMD64)
strcmp(namespaceName, "System.Runtime.Intrinsics.X86") == 0
#elif defined(TARGET_ARM64)
strcmp(namespaceName, "System.Runtime.Intrinsics.Arm") == 0
#else
0
#endif
) &&
strcmp(methodName, "get_IsSupported") == 0
)
{
GCX_COOP();
DoGetIsSupported();
didIntrinsic = true;
}
if (strcmp(methodName, "get_IsHardwareAccelerated") == 0 && strcmp(namespaceName, "System.Runtime.Intrinsics") == 0)
{
GCX_COOP();
DoGetIsSupported();
didIntrinsic = true;
}
}
#if FEATURE_SIMD
// Check for the simd class...
_ASSERTE(exactClass != NULL);
GCX_PREEMP();
bool isIntrinsicType = m_interpCeeInfo.isIntrinsicType(exactClass);
if (isIntrinsicType)
{
// SIMD intrinsics are recognized by name.
const char* namespaceName = NULL;
const char* className = NULL;
const char* methodName = getMethodName(&m_interpCeeInfo, (CORINFO_METHOD_HANDLE)methToCall, &className, &namespaceName, NULL);
if ((strcmp(methodName, "get_IsHardwareAccelerated") == 0) && (strcmp(className, "Vector") == 0) && (strcmp(namespaceName, "System.Numerics") == 0))
{
GCX_COOP();
DoSIMDHwAccelerated();
didIntrinsic = true;
}
}
if (didIntrinsic)
{
// Must block caching or we lose easy access to the class
doNotCache = true;
}
#endif // FEATURE_SIMD
}
if (didIntrinsic)
{
if (s_InterpreterUseCaching && !doNotCache)
{
// Cache the token resolution result...
pCscd = new CallSiteCacheData(methToCall, sigInfo);
CacheCallInfo(iloffset, pCscd);
}
// Now we can return.
return;
}
// Handle other simple special cases:
#if FEATURE_INTERPRETER_DEADSIMPLE_OPT
#ifndef DACCESS_COMPILE
// Dead simple static getters.
InterpreterMethodInfo* calleeInterpMethInfo;
if (GetMethodHandleToInterpMethInfoPtrMap()->Lookup(CORINFO_METHOD_HANDLE(methToCall), &calleeInterpMethInfo))
{
if (calleeInterpMethInfo->GetFlag<InterpreterMethodInfo::Flag_methIsDeadSimpleGetter>())
{
if (methToCall->IsStatic())
{
// TODO
}
else
{
ILOffsetToItemCache* calleeCache;
{
Object* thisArg = OpStackGet<Object*>(m_curStackHt-1);
GCX_FORBID();
// We pass NULL for the generic context arg, because a dead simple getter takes none, by definition.
calleeCache = calleeInterpMethInfo->GetCacheForCall(thisArg, /*genericsContextArg*/NULL);
}
// We've interpreted the getter at least once, so the cache for *some* generics context is populated -- but maybe not
// this one. We're hoping that it usually is.
if (calleeCache != NULL)
{
CachedItem cachedItem;
unsigned offsetOfLd;
if (calleeInterpMethInfo->GetFlag<InterpreterMethodInfo::Flag_methIsDeadSimpleGetterIsDbgForm>())
offsetOfLd = ILOffsetOfLdFldInDeadSimpleInstanceGetterOpt;
else
offsetOfLd = ILOffsetOfLdFldInDeadSimpleInstanceGetterOpt;
bool b = calleeCache->GetItem(offsetOfLd, cachedItem);
_ASSERTE_MSG(b, "If the cache exists for this generic context, it should an entry for the LdFld.");
_ASSERTE_MSG(cachedItem.m_tag == CIK_InstanceField, "If it's there, it should be an instance field cache.");
LdFld(cachedItem.m_value.m_instanceField);
#if INTERP_TRACING
InterlockedIncrement(&s_totalInterpCallsToDeadSimpleGetters);
InterlockedIncrement(&s_totalInterpCallsToDeadSimpleGettersShortCircuited);
#endif // INTERP_TRACING
return;
}
}
}
}
#endif // DACCESS_COMPILE
#endif // FEATURE_INTERPRETER_DEADSIMPLE_OPT
unsigned totalSigArgs;
CORINFO_VARARGS_HANDLE vaSigCookie = nullptr;
if ((sigInfo.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG ||
(sigInfo.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_NATIVEVARARG)
{
GCX_PREEMP();
CORINFO_SIG_INFO sig;
m_interpCeeInfo.findCallSiteSig(m_methInfo->m_module, methTokPtr->token, MAKE_METHODCONTEXT(m_methInfo->m_method), &sig);
sigInfo.retTypeClass = sig.retTypeClass;
sigInfo.numArgs = sig.numArgs;
sigInfo.callConv = sig.callConv;
sigInfo.retType = sig.retType;
MethodDesc* pCallSiteMD = reinterpret_cast<MethodDesc*>(m_methInfo->m_method);
sigInfo.fHasThis = CORINFO_SIG_INFO_SMALL::ComputeHasThis(pCallSiteMD);
// Adding 'this' pointer because, numArgs doesn't include the this pointer.
totalSigArgs = sigInfo.numArgs + sigInfo.hasThis();
if ((sigInfo.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG)
{
Module* module = GetModule(sig.scope);
vaSigCookie = CORINFO_VARARGS_HANDLE(module->GetVASigCookie(Signature(sig.pSig, sig.cbSig)));
}
doNotCache = true;
}
else
{
totalSigArgs = sigInfo.totalILArgs();
}
// Note that "totalNativeArgs()" includes space for ret buff arg.
unsigned nSlots = totalSigArgs + 1;
if (sigInfo.hasTypeArg()) nSlots++;
if (sigInfo.isVarArg()) nSlots++;
DelegateCtorArgs ctorData;
// If any of these are non-null, they will be pushed as extra arguments (see the code below).
ctorData.pArg3 = NULL;
ctorData.pArg4 = NULL;
ctorData.pArg5 = NULL;
// Since we make "doNotCache" true below, well never have a non-null "pCscd" for a delegate
// constructor. But we have to check for a cached method first, since callInfoPtr may be null in the cached case.
if (pCscd == NULL && callInfoPtr->classFlags & CORINFO_FLG_DELEGATE && callInfoPtr->methodFlags & CORINFO_FLG_CONSTRUCTOR)
{
// We won't cache this case.
doNotCache = true;
_ASSERTE_MSG(!sigInfo.hasTypeArg(), "I assume that this isn't possible.");
GCX_PREEMP();
ctorData.pMethod = methToCall;
// Second argument to delegate constructor will be code address of the function the delegate wraps.
_ASSERTE(TOSIsPtr() && OpStackTypeGet(m_curStackHt-1).ToCorInfoType() != CORINFO_TYPE_BYREF);
CORINFO_METHOD_HANDLE targetMethodHnd = GetFunctionPointerStack()[m_curStackHt-1];
_ASSERTE(targetMethodHnd != NULL);
CORINFO_METHOD_HANDLE alternateCtorHnd = m_interpCeeInfo.GetDelegateCtor(reinterpret_cast<CORINFO_METHOD_HANDLE>(methToCall), methTokPtr->hClass, targetMethodHnd, &ctorData);
MethodDesc* alternateCtor = reinterpret_cast<MethodDesc*>(alternateCtorHnd);
if (alternateCtor != methToCall)
{
methToCall = alternateCtor;
// Translate the method address argument from a method handle to the actual callable code address.
void* val = (void *)((MethodDesc *)targetMethodHnd)->GetMultiCallableAddrOfCode();
// Change the method argument to the code pointer.
OpStackSet<void*>(m_curStackHt-1, val);
// Now if there are extra arguments, add them to the number of slots; we'll push them on the
// arg list later.
if (ctorData.pArg3) nSlots++;
if (ctorData.pArg4) nSlots++;
if (ctorData.pArg5) nSlots++;
}
}
// Make sure that the operand stack has the required number of arguments.
// (Note that this is IL args, not native.)
//
// The total number of arguments on the IL stack. Initially we assume that all the IL arguments
// the callee expects are on the stack, but may be adjusted downwards if the "this" argument
// is provided by an allocation (the call is to a constructor).
unsigned totalArgsOnILStack = totalSigArgs;
if (m_callThisArg != NULL)
{
if (size_t(m_callThisArg) == 0x1)
{
// For string constructors, it is not necessary to pass NULL as "thisArg" to "pseudo-constructor." (#62936)
_ASSERTE(!sigInfo.hasThis());
}
else
{
_ASSERTE(totalArgsOnILStack > 0);
totalArgsOnILStack--;
}
}
#if defined(FEATURE_HFA)
// Does the callee have an HFA return type?
unsigned HFAReturnArgSlots = 0;
{
GCX_PREEMP();
if (sigInfo.retType == CORINFO_TYPE_VALUECLASS
&& (m_interpCeeInfo.getHFAType(sigInfo.retTypeClass) != CORINFO_HFA_ELEM_NONE)
&& (sigInfo.getCallConv() & CORINFO_CALLCONV_VARARG) == 0)
{
HFAReturnArgSlots = getClassSize(sigInfo.retTypeClass);
// Round up to a multiple of double size.
HFAReturnArgSlots = (HFAReturnArgSlots + sizeof(ARG_SLOT) - 1) / sizeof(ARG_SLOT);
}
}
#elif defined(UNIX_AMD64_ABI) || defined(TARGET_RISCV64)
unsigned HasTwoSlotBuf = sigInfo.retType == CORINFO_TYPE_VALUECLASS &&
getClassSize(sigInfo.retTypeClass) == 16;
#endif
// Point B
const unsigned LOCAL_ARG_SLOTS = 8;
ARG_SLOT localArgs[LOCAL_ARG_SLOTS];
InterpreterType localArgTypes[LOCAL_ARG_SLOTS];
ARG_SLOT* args;
InterpreterType* argTypes;
#if defined(HOST_X86)
unsigned totalArgSlots = nSlots;
#elif defined(HOST_ARM) || defined(HOST_ARM64)
// ARM64TODO: Verify that the following statement is correct for ARM64.
unsigned totalArgSlots = nSlots + HFAReturnArgSlots;
#elif defined(HOST_AMD64)
unsigned totalArgSlots = nSlots;
#elif defined(HOST_LOONGARCH64)
unsigned totalArgSlots = nSlots;
#elif defined(HOST_RISCV64)
unsigned totalArgSlots = nSlots;
#else
#error "unsupported platform"
#endif
if (totalArgSlots <= LOCAL_ARG_SLOTS)
{
args = &localArgs[0];
argTypes = &localArgTypes[0];
}
else
{
args = (ARG_SLOT*)_alloca(totalArgSlots * sizeof(ARG_SLOT));
#if defined(HOST_ARM)
// The HFA return buffer, if any, is assumed to be at a negative
// offset from the IL arg pointer, so adjust that pointer upward.
args = args + HFAReturnArgSlots;
#endif // defined(HOST_ARM)
argTypes = (InterpreterType*)_alloca(nSlots * sizeof(InterpreterType));
}
// Make sure that we don't scan any of these until we overwrite them with
// the real types of the arguments.
InterpreterType undefIt(CORINFO_TYPE_UNDEF);
for (unsigned i = 0; i < nSlots; i++) argTypes[i] = undefIt;
// GC-protect the argument array (as byrefs).
m_args = args; m_argsSize = nSlots; m_argTypes = argTypes;
// This is the index into the "args" array (where we copy the value to).
int curArgSlot = 0;
// The operand stack index of the first IL argument.
_ASSERTE(m_curStackHt >= totalArgsOnILStack);
int argsBase = m_curStackHt - totalArgsOnILStack;
// Current on-stack argument index.
unsigned arg = 0;
// We do "this" -- in the case of a constructor, we "shuffle" the "m_callThisArg" argument in as the first
// argument -- it isn't on the IL operand stack.
if (m_constrainedFlag)
{
_ASSERTE(m_callThisArg == NULL); // "m_callThisArg" non-null only for .ctor, which are not callvirts.
// The constrained. prefix will be immediately followed by a ldftn, call or callvirt instruction.
// See Ecma-335-Augments.md#iii21-constrained---prefix-invoke-a-member-on-a-value-of-a-variable-type-page-316 for more detail
if (sigInfo.hasThis())
{
// For the callvirt instruction, the ptr argument will be a managed pointer (&) to thisType.
CorInfoType argCIT = OpStackTypeGet(argsBase + arg).ToCorInfoType();
if (argCIT != CORINFO_TYPE_BYREF)
VerificationError("This arg of constrained call must be managed pointer.");
}
else
{
// If the constrained. prefix is applied to a call or ldftn instruction, method must be a virtual static method.
// TODO: Assert it is a virtual static method.
}
// We only cache for the CORINFO_NO_THIS_TRANSFORM case, so we may assume that if we have a cached call site,
// there's no thisTransform to perform.
if (pCscd == NULL)
{
switch (callInfoPtr->thisTransform)
{
case CORINFO_NO_THIS_TRANSFORM:
// It is a constrained call on a method implemented by a value type; this is already the proper managed pointer.
break;
case CORINFO_DEREF_THIS:
#ifdef _DEBUG
{
GCX_PREEMP();
DWORD clsAttribs = m_interpCeeInfo.getClassAttribs(m_constrainedResolvedToken.hClass);
_ASSERTE((clsAttribs & CORINFO_FLG_VALUECLASS) == 0);
}
#endif // _DEBUG
{
// As per the spec, dereference the byref to the "this" pointer, and substitute it as the new "this" pointer.
GCX_FORBID();
Object** objPtrPtr = OpStackGet<Object**>(argsBase + arg);
OpStackSet<Object*>(argsBase + arg, *objPtrPtr);
OpStackTypeSet(argsBase + arg, InterpreterType(CORINFO_TYPE_CLASS));
}
doNotCache = true;
break;
case CORINFO_BOX_THIS:
// This is the case where the call is to a virtual method of Object the given
// struct class does not override -- the struct must be boxed, so that the
// method can be invoked as a virtual.
BoxStructRefAt(argsBase + arg, m_constrainedResolvedToken.hClass);
doNotCache = true;
break;
}
exactClass = m_constrainedResolvedToken.hClass;
{
GCX_PREEMP();
DWORD exactClassAttribs = m_interpCeeInfo.getClassAttribs(exactClass);
// If the constraint type is a value class, then it is the exact class (which will be the
// "owner type" in the MDCS below.) If it is not, leave it as the (precise) interface method.
if (exactClassAttribs & CORINFO_FLG_VALUECLASS)
{
MethodTable* exactClassMT = GetMethodTableFromClsHnd(exactClass);
// Find the method on exactClass corresponding to methToCall.
methToCall = MethodDesc::FindOrCreateAssociatedMethodDesc(
reinterpret_cast<MethodDesc*>(callInfoPtr->hMethod), // pPrimaryMD
exactClassMT, // pExactMT
FALSE, // forceBoxedEntryPoint
methToCall->GetMethodInstantiation(), // methodInst
FALSE); // allowInstParam
}
else
{
exactClass = methTokPtr->hClass;
}
}
}
// We've consumed the constraint, so reset the flag.
m_constrainedFlag = false;
}
if (pCscd == NULL)
{
if (callInfoPtr->methodFlags & CORINFO_FLG_STATIC)
{
MethodDesc* pMD = reinterpret_cast<MethodDesc*>(callInfoPtr->hMethod);
EnsureClassInit(pMD->GetMethodTable());
}
}
// Point C
// We must do anything that might make a COOP->PREEMP transition before copying arguments out of the
// operand stack (where they are GC-protected) into the args array (where they are not).
#ifdef _DEBUG
const char* clsOfMethToCallName;;
const char* methToCallName = NULL;
{
GCX_PREEMP();
methToCallName = getMethodName(&m_interpCeeInfo, CORINFO_METHOD_HANDLE(methToCall), &clsOfMethToCallName);
}
#if INTERP_TRACING
if (strncmp(methToCallName, "get_", 4) == 0)
{
InterlockedIncrement(&s_totalInterpCallsToGetters);
size_t offsetOfLd;
if (IsDeadSimpleGetter(&m_interpCeeInfo, methToCall, &offsetOfLd))
{
InterlockedIncrement(&s_totalInterpCallsToDeadSimpleGetters);
}
}
else if (strncmp(methToCallName, "set_", 4) == 0)
{
InterlockedIncrement(&s_totalInterpCallsToSetters);
}
#endif // INTERP_TRACING
// Only do this check on the first call, since it should be the same each time.
if (pCscd == NULL)
{
// Ensure that any value types used as argument types are loaded. This property is checked
// by the MethodDescCall site mechanisms. Since enums are freely convertible with their underlying
// integer type, this is at least one case where a caller may push a value convertible to a value type
// without any code having caused the value type to be loaded. This is DEBUG-only because if the callee
// the integer-type value as the enum value type, it will have loaded the value type.
MetaSig ms(methToCall);
CorElementType argType;
while ((argType = ms.NextArg()) != ELEMENT_TYPE_END)
{
if (argType == ELEMENT_TYPE_VALUETYPE)
{
TypeHandle th = ms.GetLastTypeHandleThrowing(ClassLoader::LoadTypes);
CONSISTENCY_CHECK(th.CheckFullyLoaded());
}
}
}
#endif
// CYCLE PROFILE: BEFORE ARG PROCESSING.
if (sigInfo.hasThis())
{
_ASSERTE(size_t(m_callThisArg) != 0x1); // Ensure m_callThisArg is not a Special "thisArg" argument for String constructors.
if (m_callThisArg != NULL)
{
args[curArgSlot] = PtrToArgSlot(m_callThisArg);
argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_BYREF);
}
else
{
args[curArgSlot] = PtrToArgSlot(OpStackGet<void*>(argsBase + arg));
argTypes[curArgSlot] = OpStackTypeGet(argsBase + arg);
arg++;
}
// AV -> NullRef translation is NYI for the interpreter,
// so we should manually check and throw the correct exception.
if (args[curArgSlot] == NULL)
{
// If we're calling a constructor, we bypass this check since the runtime
// should have thrown OOM if it was unable to allocate an instance.
if (m_callThisArg == NULL)
{
_ASSERTE(!methToCall->IsStatic());
ThrowNullPointerException();
}
// ...except in the case of strings, which are both
// allocated and initialized by their special constructor.
else
{
_ASSERTE(methToCall->IsCtor() && methToCall->GetMethodTable()->IsString());
}
}
curArgSlot++;
}
// This is the argument slot that will be used to hold the return value.
// In UNIX_AMD64_ABI, return type may have need tow ARG_SLOTs.
ARG_SLOT retVals[2] = {0, 0};
#if !defined(HOST_ARM) && !defined(UNIX_AMD64_ABI) && !defined(TARGET_RISCV64)
_ASSERTE (NUMBER_RETURNVALUE_SLOTS == 1);
#endif
// If the return type is a structure, then these will be initialized.
CORINFO_CLASS_HANDLE retTypeClsHnd = NULL;
InterpreterType retTypeIt;
size_t retTypeSz = 0;
// If non-null, space allocated to hold a large struct return value. Should be deleted later.
// (I could probably optimize this pop all the arguments first, then allocate space for the return value
// on the large structure operand stack, and pass a pointer directly to that space, avoiding the extra
// copy we have below. But this seemed more expedient, and this should be a pretty rare case.)
BYTE* pLargeStructRetVal = NULL;
// If there's a "GetFlag<Flag_hasRetBuffArg>()" struct return value, it will be stored in this variable if it fits,
// otherwise, we'll dynamically allocate memory for it.
ARG_SLOT smallStructRetVal = 0;
// We should have no return buffer temp space registered here...unless this is a constructor, in which
// case it will return void. In particular, if the return type VALUE_CLASS, then this should be NULL.
_ASSERTE_MSG((pCscd != NULL) || sigInfo.retType == CORINFO_TYPE_VOID || m_structRetValITPtr == NULL, "Invariant.");
// Is it the return value a struct with a ret buff?
_ASSERTE_MSG(methToCall != NULL, "assumption");
bool hasRetBuffArg = false;
if (sigInfo.retType == CORINFO_TYPE_VALUECLASS || sigInfo.retType == CORINFO_TYPE_REFANY)
{
hasRetBuffArg = !!methToCall->HasRetBuffArg();
retTypeClsHnd = sigInfo.retTypeClass;
MetaSig ms(methToCall);
// On ARM, if there's an HFA return type, we must also allocate a return buffer, since the
// MDCS calling convention requires it.
if (hasRetBuffArg
#if defined(HOST_ARM)
|| HFAReturnArgSlots > 0
#endif // defined(HOST_ARM)
)
{
_ASSERTE(retTypeClsHnd != NULL);
retTypeIt = InterpreterType(&m_interpCeeInfo, retTypeClsHnd);
retTypeSz = retTypeIt.Size(&m_interpCeeInfo);
#if defined(HOST_ARM)
if (HFAReturnArgSlots > 0)
{
args[curArgSlot] = PtrToArgSlot(args - HFAReturnArgSlots);
}
else
#endif // defined(HOST_ARM)
if (retTypeIt.IsLargeStruct(&m_interpCeeInfo))
{
size_t retBuffSize = retTypeSz;
// If the target architecture can sometimes return a struct in several registers,
// MethodDescCallSite will reserve a return value array big enough to hold the maximum.
// It will then copy *all* of this into the return buffer area we allocate. So make sure
// we allocate at least that much.
#ifdef ENREGISTERED_RETURNTYPE_MAXSIZE
retBuffSize = max(retTypeSz, ENREGISTERED_RETURNTYPE_MAXSIZE);
#endif // ENREGISTERED_RETURNTYPE_MAXSIZE
pLargeStructRetVal = (BYTE*)_alloca(retBuffSize);
// Clear this in case a GC happens.
for (unsigned i = 0; i < retTypeSz; i++) pLargeStructRetVal[i] = 0;
// Register this as location needing GC.
m_structRetValTempSpace = pLargeStructRetVal;
// Set it as the return buffer.
args[curArgSlot] = PtrToArgSlot(pLargeStructRetVal);
}
else
{
// Clear this in case a GC happens.
smallStructRetVal = 0;
// Register this as location needing GC.
m_structRetValTempSpace = &smallStructRetVal;
// Set it as the return buffer.
args[curArgSlot] = PtrToArgSlot(&smallStructRetVal);
}
m_structRetValITPtr = &retTypeIt;
argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
curArgSlot++;
}
else
{
// The struct type might "normalize" to a primitive type.
if (retTypeClsHnd == NULL)
{
retTypeIt = InterpreterType(CEEInfo::asCorInfoType(ms.GetReturnTypeNormalized()));
}
else
{
retTypeIt = InterpreterType(&m_interpCeeInfo, retTypeClsHnd);
}
}
}
if (((sigInfo.callConv & CORINFO_CALLCONV_VARARG) != 0) && sigInfo.isVarArg())
{
_ASSERTE(vaSigCookie != nullptr);
args[curArgSlot] = PtrToArgSlot(vaSigCookie);
argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
curArgSlot++;
}
if (pCscd == NULL)
{
if (sigInfo.hasTypeArg())
{
GCX_PREEMP();
// We will find the instantiating stub for the method, and call that instead.
CORINFO_SIG_INFO sigInfoFull;
Instantiation methodInst = methToCall->GetMethodInstantiation();
BOOL fNeedUnboxingStub = virtualCall && TypeHandle(exactClass).IsValueType() && methToCall->IsVirtual();
methToCall = MethodDesc::FindOrCreateAssociatedMethodDesc(methToCall,
TypeHandle(exactClass).GetMethodTable(), fNeedUnboxingStub, methodInst, FALSE, TRUE);
m_interpCeeInfo.getMethodSig(CORINFO_METHOD_HANDLE(methToCall), &sigInfoFull, nullptr);
sigInfo.retTypeClass = sigInfoFull.retTypeClass;
sigInfo.numArgs = sigInfoFull.numArgs;
sigInfo.callConv = sigInfoFull.callConv;
sigInfo.retType = sigInfoFull.retType;
sigInfo.fHasThis = CORINFO_SIG_INFO_SMALL::ComputeHasThis(methToCall);
}
if (sigInfo.hasTypeArg())
{
// If we still have a type argument, we're calling an ArrayOpStub and need to pass the array TypeHandle.
_ASSERTE(methToCall->IsArray());
doNotCache = true;
args[curArgSlot] = PtrToArgSlot(exactClass);
argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
curArgSlot++;
}
}
// Now we do the non-this arguments.
size_t largeStructSpaceToPop = 0;
for (; arg < totalArgsOnILStack; arg++)
{
InterpreterType argIt = OpStackTypeGet(argsBase + arg);
size_t sz = OpStackTypeGet(argsBase + arg).Size(&m_interpCeeInfo);
switch (sz)
{
case 1:
args[curArgSlot] = OpStackGet<INT8>(argsBase + arg);
break;
case 2:
args[curArgSlot] = OpStackGet<INT16>(argsBase + arg);
break;
case 4:
args[curArgSlot] = OpStackGet<INT32>(argsBase + arg);
break;
case 8:
default:
if (sz > 8)
{
void* srcPtr = OpStackGet<void*>(argsBase + arg);
args[curArgSlot] = PtrToArgSlot(srcPtr);
if (!IsInLargeStructLocalArea(srcPtr))
largeStructSpaceToPop += sz;
}
else
{
args[curArgSlot] = OpStackGet<INT64>(argsBase + arg);
}
break;
}
argTypes[curArgSlot] = argIt;
curArgSlot++;
}
if (ctorData.pArg3)
{
args[curArgSlot] = PtrToArgSlot(ctorData.pArg3);
argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
curArgSlot++;
}
if (ctorData.pArg4)
{
args[curArgSlot] = PtrToArgSlot(ctorData.pArg4);
argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
curArgSlot++;
}
if (ctorData.pArg5)
{
args[curArgSlot] = PtrToArgSlot(ctorData.pArg5);
argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
curArgSlot++;
}
// CYCLE PROFILE: AFTER ARG PROCESSING.
{
Thread* thr = GetThread();
Object** thisArgHnd = NULL;
ARG_SLOT nullThisArg = NULL;
if (sigInfo.hasThis())
{
if (m_callThisArg != NULL)
{
if (size_t(m_callThisArg) == 0x1)
{
thisArgHnd = reinterpret_cast<Object**>(&nullThisArg);
}
else
{
thisArgHnd = reinterpret_cast<Object**>(&m_callThisArg);
}
}
else
{
thisArgHnd = OpStackGetAddr<Object*>(argsBase);
}
}
Frame* topFrameBefore = thr->GetFrame();
#if INTERP_ILCYCLE_PROFILE
unsigned __int64 startCycles;
#endif // INTERP_ILCYCLE_PROFILE
// CYCLE PROFILE: BEFORE MDCS CREATION.
PCODE target = NULL;
MethodDesc *exactMethToCall = methToCall;
// Determine the target of virtual calls.
if (virtualCall && methToCall->IsVtableMethod())
{
PCODE pCode;
_ASSERTE(thisArgHnd != NULL);
OBJECTREF objRef = ObjectToOBJECTREF(*thisArgHnd);
GCPROTECT_BEGIN(objRef);
pCode = methToCall->GetMultiCallableAddrOfVirtualizedCode(&objRef, methToCall->GetMethodTable());
GCPROTECT_END();
exactMethToCall = Entry2MethodDesc(pCode, objRef->GetMethodTable());
}
// Compile the target in advance of calling.
if (exactMethToCall->IsPointingToPrestub())
{
MethodTable* dispatchingMT = NULL;
if (exactMethToCall->IsVtableMethod())
{
_ASSERTE(thisArgHnd != NULL);
dispatchingMT = (*thisArgHnd)->GetMethodTable();
}
GCX_PREEMP();
target = exactMethToCall->DoPrestub(dispatchingMT);
}
else
{
target = exactMethToCall->GetMethodEntryPoint();
}
// If we're interpreting the method, simply call it directly.
if (InterpretationStubToMethodInfo(target) == exactMethToCall)
{
_ASSERTE(!exactMethToCall->IsILStub());
InterpreterMethodInfo* methInfo = MethodHandleToInterpreterMethInfoPtr(CORINFO_METHOD_HANDLE(exactMethToCall));
_ASSERTE(methInfo != NULL);
#if INTERP_ILCYCLE_PROFILE
bool b = CycleTimer::GetThreadCyclesS(&startCycles); _ASSERTE(b);
#endif // INTERP_ILCYCLE_PROFILE
retVals[0] = InterpretMethodBody(methInfo, true, reinterpret_cast<BYTE*>(args), NULL);
pCscd = NULL; // Nothing to cache.
}
else
{
MetaSig msig(exactMethToCall);
// We've already resolved the virtual call target above, so there is no need to do it again.
MethodDescCallSite mdcs(exactMethToCall, &msig, target);
#if INTERP_ILCYCLE_PROFILE
bool b = CycleTimer::GetThreadCyclesS(&startCycles); _ASSERTE(b);
#endif // INTERP_ILCYCLE_PROFILE
#if defined(UNIX_AMD64_ABI) || defined(TARGET_RISCV64)
mdcs.CallTargetWorker(args, retVals, HasTwoSlotBuf ? 16: 8);
#else
mdcs.CallTargetWorker(args, retVals, 8);
#endif
if (pCscd != NULL)
{
// We will do a check at the end to determine whether to cache pCscd, to set
// to NULL here to make sure we don't.
pCscd = NULL;
}
else
{
// For now, we won't cache virtual calls to virtual methods.
// TODO: fix this somehow.
if (virtualCall && (callInfoPtr->methodFlags & CORINFO_FLG_VIRTUAL)) doNotCache = true;
if (s_InterpreterUseCaching && !doNotCache)
{
// We will add this to the cache later; the locking provokes a GC,
// and "retVal" is vulnerable.
pCscd = new CallSiteCacheData(exactMethToCall, sigInfo);
}
}
}
#if INTERP_ILCYCLE_PROFILE
unsigned __int64 endCycles;
bool b = CycleTimer::GetThreadCyclesS(&endCycles); _ASSERTE(b);
m_exemptCycles += (endCycles - startCycles);
#endif // INTERP_ILCYCLE_PROFILE
// retVal is now vulnerable.
GCX_FORBID();
// Some managed methods, believe it or not, can push capital-F Frames on the Frame chain.
// If this happens, executing the EX_CATCH below will pop it, which is bad.
// So detect that case, pop the explicitly-pushed frame, and push it again after the EX_CATCH.
// (Asserting that there is only 1 such frame!)
if (thr->GetFrame() != topFrameBefore)
{
ilPushedFrame = thr->GetFrame();
if (ilPushedFrame != NULL)
{
ilPushedFrame->Pop(thr);
if (thr->GetFrame() != topFrameBefore)
{
// This wasn't an IL-pushed frame, so restore.
ilPushedFrame->Push(thr);
ilPushedFrame = NULL;
}
}
}
}
// retVal is still vulnerable.
{
GCX_FORBID();
m_argsSize = 0;
// At this point, the call has happened successfully. We can delete the arguments from the operand stack.
m_curStackHt -= totalArgsOnILStack;
// We've already checked that "largeStructSpaceToPop
LargeStructOperandStackPop(largeStructSpaceToPop, NULL);
if (size_t(m_callThisArg) == 0x1)
{
_ASSERTE_MSG(sigInfo.retType == CORINFO_TYPE_VOID, "Constructor for var-sized object becomes factory method that returns result.");
OpStackSet<Object*>(m_curStackHt, reinterpret_cast<Object*>(retVals[0]));
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_CLASS));
m_curStackHt++;
}
else if (sigInfo.retType != CORINFO_TYPE_VOID)
{
switch (sigInfo.retType)
{
case CORINFO_TYPE_BOOL:
case CORINFO_TYPE_BYTE:
OpStackSet<INT32>(m_curStackHt, static_cast<INT8>(retVals[0]));
break;
case CORINFO_TYPE_UBYTE:
OpStackSet<UINT32>(m_curStackHt, static_cast<UINT8>(retVals[0]));
break;
case CORINFO_TYPE_SHORT:
OpStackSet<INT32>(m_curStackHt, static_cast<INT16>(retVals[0]));
break;
case CORINFO_TYPE_USHORT:
case CORINFO_TYPE_CHAR:
OpStackSet<UINT32>(m_curStackHt, static_cast<UINT16>(retVals[0]));
break;
case CORINFO_TYPE_INT:
case CORINFO_TYPE_UINT:
case CORINFO_TYPE_FLOAT:
OpStackSet<INT32>(m_curStackHt, static_cast<INT32>(retVals[0]));
break;
case CORINFO_TYPE_LONG:
case CORINFO_TYPE_ULONG:
case CORINFO_TYPE_DOUBLE:
OpStackSet<INT64>(m_curStackHt, static_cast<INT64>(retVals[0]));
break;
case CORINFO_TYPE_NATIVEINT:
case CORINFO_TYPE_NATIVEUINT:
case CORINFO_TYPE_PTR:
OpStackSet<NativeInt>(m_curStackHt, static_cast<NativeInt>(retVals[0]));
break;
case CORINFO_TYPE_CLASS:
OpStackSet<Object*>(m_curStackHt, reinterpret_cast<Object*>(retVals[0]));
break;
case CORINFO_TYPE_BYREF:
OpStackSet<void*>(m_curStackHt, reinterpret_cast<void*>(retVals[0]));
break;
case CORINFO_TYPE_VALUECLASS:
case CORINFO_TYPE_REFANY:
{
// We must be careful here to write the value, the type, and update the stack height in one
// sequence that has no COOP->PREEMP transitions in it, so no GC's happen until the value
// is protected by being fully "on" the operandStack.
#if defined(HOST_ARM)
// Is the return type an HFA?
if (HFAReturnArgSlots > 0)
{
ARG_SLOT* hfaRetBuff = args - HFAReturnArgSlots;
if (retTypeIt.IsLargeStruct(&m_interpCeeInfo))
{
void* dst = LargeStructOperandStackPush(retTypeSz);
memcpy(dst, hfaRetBuff, retTypeSz);
OpStackSet<void*>(m_curStackHt, dst);
}
else
{
memcpy(OpStackGetAddr<UINT64>(m_curStackHt), hfaRetBuff, retTypeSz);
}
}
else
#endif // defined(HOST_ARM)
if (pLargeStructRetVal != NULL)
{
_ASSERTE(hasRetBuffArg);
void* dst = LargeStructOperandStackPush(retTypeSz);
CopyValueClassUnchecked(dst, pLargeStructRetVal, GetMethodTableFromClsHnd(retTypeClsHnd));
OpStackSet<void*>(m_curStackHt, dst);
}
else if (hasRetBuffArg)
{
OpStackSet<INT64>(m_curStackHt, GetSmallStructValue(&smallStructRetVal, retTypeSz));
}
#if defined(UNIX_AMD64_ABI) || defined(TARGET_RISCV64)
else if (HasTwoSlotBuf)
{
void* dst = LargeStructOperandStackPush(16);
CopyValueClassUnchecked(dst, retVals, GetMethodTableFromClsHnd(retTypeClsHnd));
OpStackSet<void*>(m_curStackHt, dst);
}
#endif
else
{
OpStackSet<UINT64>(m_curStackHt, retVals[0]);
}
// We already created this interpreter type, so use it.
OpStackTypeSet(m_curStackHt, retTypeIt.StackNormalize());
m_curStackHt++;
// In the value-class case, the call might have used a ret buff, which we would have registered for GC scanning.
// Make sure it's unregistered.
m_structRetValITPtr = NULL;
}
break;
default:
NYI_INTERP("Unhandled return type");
break;
}
_ASSERTE_MSG(m_structRetValITPtr == NULL, "Invariant.");
// The valueclass case is handled fully in the switch above.
if (sigInfo.retType != CORINFO_TYPE_VALUECLASS &&
sigInfo.retType != CORINFO_TYPE_REFANY)
{
OpStackTypeSet(m_curStackHt, InterpreterType(sigInfo.retType).StackNormalize());
m_curStackHt++;
}
}
}
// Originally, this assertion was in the ValueClass case above, but it does a COOP->PREEMP
// transition, and therefore causes a GC, and we're GCX_FORBIDden from doing a GC while retVal
// is vulnerable. So, for completeness, do it here.
_ASSERTE(sigInfo.retType != CORINFO_TYPE_VALUECLASS || retTypeIt == InterpreterType(&m_interpCeeInfo, retTypeClsHnd));
// If we created a cached call site, cache it now (when it's safe to take a GC).
if (pCscd != NULL && !doNotCache)
{
CacheCallInfo(iloffset, pCscd);
}
m_callThisArg = NULL;
// If the call we just made pushed a Frame, we popped it above, so re-push it.
if (ilPushedFrame != NULL) ilPushedFrame->Push();
}
#include "metadata.h"
void Interpreter::CallI()
{
#if INTERP_DYNAMIC_CONTRACTS
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#else
// Dynamic contract occupies too much stack.
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_COOPERATIVE;
#endif
#if INTERP_TRACING
InterlockedIncrement(&s_totalInterpCalls);
#endif // INTERP_TRACING
unsigned tok = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));
CORINFO_SIG_INFO sigInfo;
{
GCX_PREEMP();
m_interpCeeInfo.findSig(m_methInfo->m_module, tok, GetPreciseGenericsContext(), &sigInfo);
}
// I'm assuming that a calli can't depend on the generics context, so the simple form of type
// context should suffice?
MethodDesc* pMD = reinterpret_cast<MethodDesc*>(m_methInfo->m_method);
SigTypeContext sigTypeCtxt(pMD);
MetaSig mSig(sigInfo.pSig, sigInfo.cbSig, GetModule(sigInfo.scope), &sigTypeCtxt);
unsigned totalSigArgs = sigInfo.totalILArgs();
// Note that "totalNativeArgs()" includes space for ret buff arg.
unsigned nSlots = totalSigArgs + 1;
if ((sigInfo.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG)
{
nSlots++;
}
// Make sure that the operand stack has the required number of arguments.
// (Note that this is IL args, not native.)
//
// The total number of arguments on the IL stack. Initially we assume that all the IL arguments
// the callee expects are on the stack, but may be adjusted downwards if the "this" argument
// is provided by an allocation (the call is to a constructor).
unsigned totalArgsOnILStack = totalSigArgs;
const unsigned LOCAL_ARG_SLOTS = 8;
ARG_SLOT localArgs[LOCAL_ARG_SLOTS];
InterpreterType localArgTypes[LOCAL_ARG_SLOTS];
ARG_SLOT* args;
InterpreterType* argTypes;
if (nSlots <= LOCAL_ARG_SLOTS)
{
args = &localArgs[0];
argTypes = &localArgTypes[0];
}
else
{
args = (ARG_SLOT*)_alloca(nSlots * sizeof(ARG_SLOT));
argTypes = (InterpreterType*)_alloca(nSlots * sizeof(InterpreterType));
}
// Make sure that we don't scan any of these until we overwrite them with
// the real types of the arguments.
InterpreterType undefIt(CORINFO_TYPE_UNDEF);
for (unsigned i = 0; i < nSlots; i++)
{
argTypes[i] = undefIt;
}
// GC-protect the argument array (as byrefs).
m_args = args;
m_argsSize = nSlots;
m_argTypes = argTypes;
// This is the index into the "args" array (where we copy the value to).
int curArgSlot = 0;
// The operand stack index of the first IL argument.
unsigned totalArgPositions = totalArgsOnILStack + 1; // + 1 for the ftn argument.
_ASSERTE(m_curStackHt >= totalArgPositions);
int argsBase = m_curStackHt - totalArgPositions;
// Current on-stack argument index.
unsigned arg = 0;
if (sigInfo.hasThis())
{
args[curArgSlot] = PtrToArgSlot(OpStackGet<void*>(argsBase + arg));
argTypes[curArgSlot] = OpStackTypeGet(argsBase + arg);
// AV -> NullRef translation is NYI for the interpreter,
// so we should manually check and throw the correct exception.
ThrowOnInvalidPointer((void*)args[curArgSlot]);
arg++;
curArgSlot++;
}
// This is the argument slot that will be used to hold the return value.
ARG_SLOT retVal = 0;
// If the return type is a structure, then these will be initialized.
CORINFO_CLASS_HANDLE retTypeClsHnd = NULL;
InterpreterType retTypeIt;
size_t retTypeSz = 0;
// If non-null, space allocated to hold a large struct return value. Should be deleted later.
// (I could probably optimize this pop all the arguments first, then allocate space for the return value
// on the large structure operand stack, and pass a pointer directly to that space, avoiding the extra
// copy we have below. But this seemed more expedient, and this should be a pretty rare case.)
BYTE* pLargeStructRetVal = NULL;
// If there's a "GetFlag<Flag_hasRetBuffArg>()" struct return value, it will be stored in this variable if it fits,
// otherwise, we'll dynamically allocate memory for it.
ARG_SLOT smallStructRetVal = 0;
// We should have no return buffer temp space registered here...unless this is a constructor, in which
// case it will return void. In particular, if the return type VALUE_CLASS, then this should be NULL.
_ASSERTE_MSG(sigInfo.retType == CORINFO_TYPE_VOID || m_structRetValITPtr == NULL, "Invariant.");
// Is it the return value a struct with a ret buff?
bool hasRetBuffArg = false;
if (sigInfo.retType == CORINFO_TYPE_VALUECLASS)
{
retTypeClsHnd = sigInfo.retTypeClass;
retTypeIt = InterpreterType(&m_interpCeeInfo, retTypeClsHnd);
retTypeSz = retTypeIt.Size(&m_interpCeeInfo);
#if defined(UNIX_AMD64_ABI)
//
#elif defined(HOST_AMD64)
// TODO: Investigate why HasRetBuffArg can't be used. pMD is a hacked up MD for the
// calli because it belongs to the current method. Doing what the JIT does.
hasRetBuffArg = (retTypeSz > sizeof(void*)) || ((retTypeSz & (retTypeSz - 1)) != 0);
#else
hasRetBuffArg = !!pMD->HasRetBuffArg();
#endif
if (hasRetBuffArg)
{
if (retTypeIt.IsLargeStruct(&m_interpCeeInfo))
{
size_t retBuffSize = retTypeSz;
// If the target architecture can sometimes return a struct in several registers,
// MethodDescCallSite will reserve a return value array big enough to hold the maximum.
// It will then copy *all* of this into the return buffer area we allocate. So make sure
// we allocate at least that much.
#ifdef ENREGISTERED_RETURNTYPE_MAXSIZE
retBuffSize = max(retTypeSz, ENREGISTERED_RETURNTYPE_MAXSIZE);
#endif // ENREGISTERED_RETURNTYPE_MAXSIZE
pLargeStructRetVal = (BYTE*)_alloca(retBuffSize);
// Clear this in case a GC happens.
for (unsigned i = 0; i < retTypeSz; i++)
{
pLargeStructRetVal[i] = 0;
}
// Register this as location needing GC.
m_structRetValTempSpace = pLargeStructRetVal;
// Set it as the return buffer.
args[curArgSlot] = PtrToArgSlot(pLargeStructRetVal);
}
else
{
// Clear this in case a GC happens.
smallStructRetVal = 0;
// Register this as location needing GC.
m_structRetValTempSpace = &smallStructRetVal;
// Set it as the return buffer.
args[curArgSlot] = PtrToArgSlot(&smallStructRetVal);
}
m_structRetValITPtr = &retTypeIt;
argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
curArgSlot++;
}
}
if ((sigInfo.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG)
{
Module* module = GetModule(sigInfo.scope);
CORINFO_VARARGS_HANDLE handle = CORINFO_VARARGS_HANDLE(module->GetVASigCookie(Signature(sigInfo.pSig, sigInfo.cbSig)));
args[curArgSlot] = PtrToArgSlot(handle);
argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
curArgSlot++;
}
// Now we do the non-this arguments.
size_t largeStructSpaceToPop = 0;
for (; arg < totalArgsOnILStack; arg++)
{
InterpreterType argIt = OpStackTypeGet(argsBase + arg);
size_t sz = OpStackTypeGet(argsBase + arg).Size(&m_interpCeeInfo);
switch (sz)
{
case 1:
args[curArgSlot] = OpStackGet<INT8>(argsBase + arg);
break;
case 2:
args[curArgSlot] = OpStackGet<INT16>(argsBase + arg);
break;
case 4:
args[curArgSlot] = OpStackGet<INT32>(argsBase + arg);
break;
case 8:
default:
if (sz > 8)
{
void* srcPtr = OpStackGet<void*>(argsBase + arg);
args[curArgSlot] = PtrToArgSlot(srcPtr);
if (!IsInLargeStructLocalArea(srcPtr))
{
largeStructSpaceToPop += sz;
}
}
else
{
args[curArgSlot] = OpStackGet<INT64>(argsBase + arg);
}
break;
}
argTypes[curArgSlot] = argIt;
curArgSlot++;
}
// Finally, we get the code pointer.
unsigned ftnInd = m_curStackHt - 1;
#ifdef _DEBUG
CorInfoType ftnType = OpStackTypeGet(ftnInd).ToCorInfoType();
_ASSERTE(ftnType == CORINFO_TYPE_NATIVEINT
|| ftnType == CORINFO_TYPE_INT
|| ftnType == CORINFO_TYPE_LONG);
#endif // DEBUG
PCODE ftnPtr = OpStackGet<PCODE>(ftnInd);
{
MethodDesc* methToCall;
// If we're interpreting the target, simply call it directly.
if ((methToCall = InterpretationStubToMethodInfo((PCODE)ftnPtr)) != NULL)
{
InterpreterMethodInfo* methInfo = MethodHandleToInterpreterMethInfoPtr(CORINFO_METHOD_HANDLE(methToCall));
_ASSERTE(methInfo != NULL);
#if INTERP_ILCYCLE_PROFILE
bool b = CycleTimer::GetThreadCyclesS(&startCycles); _ASSERTE(b);
#endif // INTERP_ILCYCLE_PROFILE
retVal = InterpretMethodBody(methInfo, true, reinterpret_cast<BYTE*>(args), NULL);
}
else
{
// This is not a great workaround. For the most part, we really don't care what method desc we're using, since
// we're providing the signature and function pointer -- other than that it's well-formed and "activated."
// And also, one more thing: whether it is static or not. Which is actually determined by the signature.
// So we query the signature we have to determine whether we need a static or instance MethodDesc, and then
// use one of the appropriate staticness that happens to be sitting around in global variables. For static
// we use "RuntimeHelpers.PrepareConstrainedRegions", for instance we use the default constructor of "Object."
// TODO: make this cleaner -- maybe invent a couple of empty methods with instructive names, just for this purpose.
MethodDesc* pMD;
if (mSig.HasThis())
{
pMD = g_pObjectFinalizerMD;
}
else
{
pMD = CoreLibBinder::GetMethod(METHOD__INTERLOCKED__COMPARE_EXCHANGE_OBJECT); // A random static method.
}
MethodDescCallSite mdcs(pMD, &mSig, ftnPtr);
#if 0
// If the current method being interpreted is an IL stub, we're calling native code, so
// change the GC mode. (We'll only do this at the call if the calling convention turns out
// to be a managed calling convention.)
MethodDesc* pStubContextMD = reinterpret_cast<MethodDesc*>(m_stubContext);
bool transitionToPreemptive = (pStubContextMD != NULL && !pStubContextMD->IsIL());
mdcs.CallTargetWorker(args, &retVal, sizeof(retVal), transitionToPreemptive);
#else
// TODO The code above triggers assertion at threads.cpp:6861:
// _ASSERTE(thread->PreemptiveGCDisabled()); // Should have been in managed code
// The workaround will likely break more things than what it is fixing:
// just do not make transition to preemptive GC for now.
mdcs.CallTargetWorker(args, &retVal, sizeof(retVal));
#endif
}
// retVal is now vulnerable.
GCX_FORBID();
}
// retVal is still vulnerable.
{
GCX_FORBID();
m_argsSize = 0;
// At this point, the call has happened successfully. We can delete the arguments from the operand stack.
m_curStackHt -= totalArgPositions;
// We've already checked that "largeStructSpaceToPop
LargeStructOperandStackPop(largeStructSpaceToPop, NULL);
if (size_t(m_callThisArg) == 0x1)
{
_ASSERTE_MSG(sigInfo.retType == CORINFO_TYPE_VOID, "Constructor for var-sized object becomes factory method that returns result.");
OpStackSet<Object*>(m_curStackHt, reinterpret_cast<Object*>(retVal));
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_CLASS));
m_curStackHt++;
}
else if (sigInfo.retType != CORINFO_TYPE_VOID)
{
switch (sigInfo.retType)
{
case CORINFO_TYPE_BOOL:
case CORINFO_TYPE_BYTE:
OpStackSet<INT32>(m_curStackHt, static_cast<INT8>(retVal));
break;
case CORINFO_TYPE_UBYTE:
OpStackSet<UINT32>(m_curStackHt, static_cast<UINT8>(retVal));
break;
case CORINFO_TYPE_SHORT:
OpStackSet<INT32>(m_curStackHt, static_cast<INT16>(retVal));
break;
case CORINFO_TYPE_USHORT:
case CORINFO_TYPE_CHAR:
OpStackSet<UINT32>(m_curStackHt, static_cast<UINT16>(retVal));
break;
case CORINFO_TYPE_INT:
case CORINFO_TYPE_UINT:
case CORINFO_TYPE_FLOAT:
OpStackSet<INT32>(m_curStackHt, static_cast<INT32>(retVal));
break;
case CORINFO_TYPE_LONG:
case CORINFO_TYPE_ULONG:
case CORINFO_TYPE_DOUBLE:
OpStackSet<INT64>(m_curStackHt, static_cast<INT64>(retVal));
break;
case CORINFO_TYPE_NATIVEINT:
case CORINFO_TYPE_NATIVEUINT:
case CORINFO_TYPE_PTR:
OpStackSet<NativeInt>(m_curStackHt, static_cast<NativeInt>(retVal));
break;
case CORINFO_TYPE_CLASS:
OpStackSet<Object*>(m_curStackHt, reinterpret_cast<Object*>(retVal));
break;
case CORINFO_TYPE_VALUECLASS:
{
// We must be careful here to write the value, the type, and update the stack height in one
// sequence that has no COOP->PREEMP transitions in it, so no GC's happen until the value
// is protected by being fully "on" the operandStack.
if (pLargeStructRetVal != NULL)
{
_ASSERTE(hasRetBuffArg);
void* dst = LargeStructOperandStackPush(retTypeSz);
CopyValueClassUnchecked(dst, pLargeStructRetVal, GetMethodTableFromClsHnd(retTypeClsHnd));
OpStackSet<void*>(m_curStackHt, dst);
}
else if (hasRetBuffArg)
{
OpStackSet<INT64>(m_curStackHt, GetSmallStructValue(&smallStructRetVal, retTypeSz));
}
else
{
OpStackSet<UINT64>(m_curStackHt, retVal);
}
// We already created this interpreter type, so use it.
OpStackTypeSet(m_curStackHt, retTypeIt.StackNormalize());
m_curStackHt++;
// In the value-class case, the call might have used a ret buff, which we would have registered for GC scanning.
// Make sure it's unregistered.
m_structRetValITPtr = NULL;
}
break;
default:
NYI_INTERP("Unhandled return type");
break;
}
_ASSERTE_MSG(m_structRetValITPtr == NULL, "Invariant.");
// The valueclass case is handled fully in the switch above.
if (sigInfo.retType != CORINFO_TYPE_VALUECLASS)
{
OpStackTypeSet(m_curStackHt, InterpreterType(sigInfo.retType).StackNormalize());
m_curStackHt++;
}
}
}
// Originally, this assertion was in the ValueClass case above, but it does a COOP->PREEMP
// transition, and therefore causes a GC, and we're GCX_FORBIDden from doing a GC while retVal
// is vulnerable. So, for completeness, do it here.
_ASSERTE(sigInfo.retType != CORINFO_TYPE_VALUECLASS || retTypeIt == InterpreterType(&m_interpCeeInfo, retTypeClsHnd));
m_ILCodePtr += 5;
}
// static
bool Interpreter::IsDeadSimpleGetter(CEEInfo* info, MethodDesc* pMD, size_t* offsetOfLd)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_ANY;
} CONTRACTL_END;
DWORD flags = pMD->GetAttrs();
CORINFO_METHOD_INFO methInfo;
{
GCX_PREEMP();
bool b = info->getMethodInfo(CORINFO_METHOD_HANDLE(pMD), &methInfo, NULL);
if (!b) return false;
}
// If the method takes a generic type argument, it's not dead simple...
if (methInfo.args.callConv & CORINFO_CALLCONV_PARAMTYPE) return false;
BYTE* codePtr = methInfo.ILCode;
if (flags & CORINFO_FLG_STATIC)
{
if (methInfo.ILCodeSize != 6)
return false;
if (*codePtr != CEE_LDSFLD)
return false;
_ASSERTE(ILOffsetOfLdSFldInDeadSimpleStaticGetter == 0);
*offsetOfLd = 0;
codePtr += 5;
return (*codePtr == CEE_RET);
}
else
{
// We handle two forms, one for DBG IL, and one for OPT IL.
bool dbg = false;
if (methInfo.ILCodeSize == 0xc)
dbg = true;
else if (methInfo.ILCodeSize != 7)
return false;
if (dbg)
{
if (*codePtr != CEE_NOP)
return false;
codePtr += 1;
}
if (*codePtr != CEE_LDARG_0)
return false;
codePtr += 1;
if (*codePtr != CEE_LDFLD)
return false;
*offsetOfLd = codePtr - methInfo.ILCode;
_ASSERTE((dbg && ILOffsetOfLdFldInDeadSimpleInstanceGetterDbg == *offsetOfLd)
|| (!dbg && ILOffsetOfLdFldInDeadSimpleInstanceGetterOpt == *offsetOfLd));
codePtr += 5;
if (dbg)
{
if (*codePtr != CEE_STLOC_0)
return false;
codePtr += 1;
if (*codePtr != CEE_BR)
return false;
if (getU4LittleEndian(codePtr + 1) != 0)
return false;
codePtr += 5;
if (*codePtr != CEE_LDLOC_0)
return false;
}
return (*codePtr == CEE_RET);
}
}
void Interpreter::DoStringLength()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt > 0);
unsigned ind = m_curStackHt - 1;
#ifdef _DEBUG
CorInfoType stringCIT = OpStackTypeGet(ind).ToCorInfoType();
if (stringCIT != CORINFO_TYPE_CLASS)
{
VerificationError("StringLength called on non-string.");
}
#endif // _DEBUG
Object* obj = OpStackGet<Object*>(ind);
if (obj == NULL)
{
ThrowNullPointerException();
}
#ifdef _DEBUG
if (obj->GetMethodTable() != g_pStringClass)
{
VerificationError("StringLength called on non-string.");
}
#endif // _DEBUG
StringObject* str = reinterpret_cast<StringObject*>(obj);
INT32 len = str->GetStringLength();
OpStackSet<INT32>(ind, len);
OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_INT));
}
void Interpreter::DoStringGetChar()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt >= 2);
unsigned strInd = m_curStackHt - 2;
unsigned indexInd = strInd + 1;
#ifdef _DEBUG
CorInfoType stringCIT = OpStackTypeGet(strInd).ToCorInfoType();
if (stringCIT != CORINFO_TYPE_CLASS)
{
VerificationError("StringGetChar called on non-string.");
}
#endif // _DEBUG
Object* obj = OpStackGet<Object*>(strInd);
if (obj == NULL)
{
ThrowNullPointerException();
}
#ifdef _DEBUG
if (obj->GetMethodTable() != g_pStringClass)
{
VerificationError("StringGetChar called on non-string.");
}
#endif // _DEBUG
StringObject* str = reinterpret_cast<StringObject*>(obj);
#ifdef _DEBUG
CorInfoType indexCIT = OpStackTypeGet(indexInd).ToCorInfoType();
if (indexCIT != CORINFO_TYPE_INT)
{
VerificationError("StringGetChar needs integer index.");
}
#endif // _DEBUG
INT32 ind = OpStackGet<INT32>(indexInd);
if (ind < 0)
ThrowArrayBoundsException();
UINT32 uind = static_cast<UINT32>(ind);
if (uind >= str->GetStringLength())
ThrowArrayBoundsException();
// Otherwise...
GCX_FORBID(); // str is vulnerable.
UINT16* dataPtr = reinterpret_cast<UINT16*>(reinterpret_cast<INT8*>(str) + StringObject::GetBufferOffset());
UINT32 filledChar = dataPtr[ind];
OpStackSet<UINT32>(strInd, filledChar);
OpStackTypeSet(strInd, InterpreterType(CORINFO_TYPE_INT));
m_curStackHt = indexInd;
}
void Interpreter::DoGetTypeFromHandle()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt > 0);
unsigned ind = m_curStackHt - 1;
#ifdef _DEBUG
CorInfoType handleCIT = OpStackTypeGet(ind).ToCorInfoType();
if (handleCIT != CORINFO_TYPE_VALUECLASS && handleCIT != CORINFO_TYPE_CLASS)
{
VerificationError("HandleGetTypeFromHandle called on non-RuntimeTypeHandle/non-RuntimeType.");
}
Object* obj = OpStackGet<Object*>(ind);
if (obj->GetMethodTable() != g_pRuntimeTypeClass)
{
VerificationError("HandleGetTypeFromHandle called on non-RuntimeTypeHandle/non-RuntimeType.");
}
#endif // _DEBUG
OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_CLASS));
}
void Interpreter::DoSIMDHwAccelerated()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#if INTERP_TRACING
if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
{
fprintf(GetLogFile(), " System.Numerics.Vector.IsHardwareAccelerated -- intrinsic\n");
}
#endif // INTERP_TRACING
LdIcon(1);
}
void Interpreter::DoGetIsSupported()
{
CONTRACTL{
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
OpStackSet<BOOL>(m_curStackHt, false);
OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_INT));
m_curStackHt++;
}
void Interpreter::DoGetArrayDataReference()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
_ASSERTE(m_curStackHt > 0);
unsigned ind = m_curStackHt - 1;
#ifdef _DEBUG
_ASSERTE(OpStackTypeGet(ind).ToCorInfoType() == CORINFO_TYPE_CLASS);
#endif // _DEBUG
Object* obj = OpStackGet<Object*>(ind);
if (obj == NULL)
{
ThrowNullPointerException();
}
#ifdef _DEBUG
_ASSERTE(obj->GetMethodTable()->IsArray());
#endif // _DEBUG
ArrayBase* a = reinterpret_cast<ArrayBase*>(obj);
ThrowOnInvalidPointer(a);
PTR_BYTE dataPtr = a->GetDataPtr();
OpStackSet<void*>(ind, dataPtr);
OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_BYREF));
}
void Interpreter::RecordConstrainedCall()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#if INTERP_TRACING
InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_Constrained]);
#endif // INTERP_TRACING
{
GCX_PREEMP();
ResolveToken(&m_constrainedResolvedToken, getU4LittleEndian(m_ILCodePtr + 2), CORINFO_TOKENKIND_Constrained InterpTracingArg(RTK_Constrained));
}
m_constrainedFlag = true;
m_ILCodePtr += 6;
}
void Interpreter::LargeStructOperandStackEnsureCanPush(size_t sz)
{
size_t remaining = m_largeStructOperandStackAllocSize - m_largeStructOperandStackHt;
if (remaining < sz)
{
size_t newAllocSize = max(m_largeStructOperandStackAllocSize + sz * 4, m_largeStructOperandStackAllocSize * 2);
BYTE* newStack = new BYTE[newAllocSize];
m_largeStructOperandStackAllocSize = newAllocSize;
if (m_largeStructOperandStack != NULL)
{
memcpy(newStack, m_largeStructOperandStack, m_largeStructOperandStackHt);
delete[] m_largeStructOperandStack;
}
m_largeStructOperandStack = newStack;
}
}
void* Interpreter::LargeStructOperandStackPush(size_t sz)
{
LargeStructOperandStackEnsureCanPush(sz);
_ASSERTE(m_largeStructOperandStackAllocSize >= m_largeStructOperandStackHt + sz);
void* res = &m_largeStructOperandStack[m_largeStructOperandStackHt];
m_largeStructOperandStackHt += sz;
return res;
}
void Interpreter::LargeStructOperandStackPop(size_t sz, void* fromAddr)
{
if (!IsInLargeStructLocalArea(fromAddr))
{
_ASSERTE(m_largeStructOperandStackHt >= sz);
m_largeStructOperandStackHt -= sz;
}
}
#ifdef _DEBUG
bool Interpreter::LargeStructStackHeightIsValid()
{
size_t sz2 = 0;
for (unsigned k = 0; k < m_curStackHt; k++)
{
if (OpStackTypeGet(k).IsLargeStruct(&m_interpCeeInfo) && !IsInLargeStructLocalArea(OpStackGet<void*>(k)))
{
sz2 += OpStackTypeGet(k).Size(&m_interpCeeInfo);
}
}
_ASSERTE(sz2 == m_largeStructOperandStackHt);
return sz2 == m_largeStructOperandStackHt;
}
#endif // _DEBUG
void Interpreter::VerificationError(const char* msg)
{
// TODO: Should raise an exception eventually; for now:
const char* const msgPrefix = "Verification Error: ";
size_t len = strlen(msgPrefix) + strlen(msg) + 1;
char* msgFinal = (char*)_alloca(len);
strcpy_s(msgFinal, len, msgPrefix);
strcat_s(msgFinal, len, msg);
_ASSERTE_MSG(false, msgFinal);
}
void Interpreter::ThrowDivideByZero()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
COMPlusThrow(kDivideByZeroException);
}
void Interpreter::ThrowSysArithException()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
// According to the ECMA spec, this should be an ArithmeticException; however,
// the JITs throw an OverflowException and consistency is top priority...
COMPlusThrow(kOverflowException);
}
void Interpreter::ThrowNullPointerException()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
COMPlusThrow(kNullReferenceException);
}
void Interpreter::ThrowOverflowException()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
COMPlusThrow(kOverflowException);
}
void Interpreter::ThrowArrayBoundsException()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
COMPlusThrow(kIndexOutOfRangeException);
}
void Interpreter::ThrowInvalidCastException()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
COMPlusThrow(kInvalidCastException);
}
void Interpreter::ThrowStackOverflow()
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
COMPlusThrow(kStackOverflowException);
}
float Interpreter::RemFunc(float v1, float v2)
{
return fmodf(v1, v2);
}
double Interpreter::RemFunc(double v1, double v2)
{
return fmod(v1, v2);
}
// Static members and methods.
Interpreter::AddrToMDMap* Interpreter::s_addrToMDMap = NULL;
unsigned Interpreter::s_interpreterStubNum = 0;
// TODO: contracts and synchronization for the AddrToMDMap methods.
// Requires caller to hold "s_interpStubToMDMapLock".
Interpreter::AddrToMDMap* Interpreter::GetAddrToMdMap()
{
#if 0
CONTRACTL {
THROWS;
GC_NOTRIGGER;
} CONTRACTL_END;
#endif
if (s_addrToMDMap == NULL)
{
s_addrToMDMap = new AddrToMDMap();
}
return s_addrToMDMap;
}
void Interpreter::RecordInterpreterStubForMethodDesc(CORINFO_METHOD_HANDLE md, void* addr)
{
#if 0
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
#endif
CrstHolder ch(&s_interpStubToMDMapLock);
AddrToMDMap* map = Interpreter::GetAddrToMdMap();
#ifdef _DEBUG
CORINFO_METHOD_HANDLE dummy;
_ASSERTE(!map->Lookup(addr, &dummy));
#endif // DEBUG
map->AddOrReplace(KeyValuePair<void*,CORINFO_METHOD_HANDLE>(addr, md));
}
MethodDesc* Interpreter::InterpretationStubToMethodInfo(PCODE addr)
{
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
// This query function will never allocate the table...
if (s_addrToMDMap == NULL)
return NULL;
// Otherwise...if we observe s_addrToMdMap non-null, the lock below must be initialized.
// CrstHolder ch(&s_interpStubToMDMapLock);
AddrToMDMap* map = Interpreter::GetAddrToMdMap();
CORINFO_METHOD_HANDLE result = NULL;
(void)map->Lookup((void*)addr, &result);
return (MethodDesc*)result;
}
Interpreter::MethodHandleToInterpMethInfoPtrMap* Interpreter::s_methodHandleToInterpMethInfoPtrMap = NULL;
// Requires caller to hold "s_interpStubToMDMapLock".
Interpreter::MethodHandleToInterpMethInfoPtrMap* Interpreter::GetMethodHandleToInterpMethInfoPtrMap()
{
#if 0
CONTRACTL {
THROWS;
GC_NOTRIGGER;
} CONTRACTL_END;
#endif
if (s_methodHandleToInterpMethInfoPtrMap == NULL)
{
s_methodHandleToInterpMethInfoPtrMap = new MethodHandleToInterpMethInfoPtrMap();
}
return s_methodHandleToInterpMethInfoPtrMap;
}
InterpreterMethodInfo* Interpreter::RecordInterpreterMethodInfoForMethodHandle(CORINFO_METHOD_HANDLE md, InterpreterMethodInfo* methInfo)
{
#if 0
CONTRACTL {
NOTHROW;
GC_NOTRIGGER;
} CONTRACTL_END;
#endif
CrstHolder ch(&s_interpStubToMDMapLock);
MethodHandleToInterpMethInfoPtrMap* map = Interpreter::GetMethodHandleToInterpMethInfoPtrMap();
MethInfo mi;
if (map->Lookup(md, &mi))
{
// If there's already an entry, make sure it was created by another thread -- the same thread shouldn't create two
// of these.
_ASSERTE_MSG(mi.m_thread != GetThread(), "Two InterpMethInfo's for same meth by same thread.");
// If we were creating an interpreter stub at the same time as another thread, and we lost the race to
// insert it, use the already-existing one, and delete this one.
delete methInfo;
return mi.m_info;
}
mi.m_info = methInfo;
#ifdef _DEBUG
mi.m_thread = GetThread();
#endif
_ASSERTE_MSG(map->LookupPtr(md) == NULL, "Multiple InterpMethInfos for method desc.");
map->Add(md, mi);
return methInfo;
}
InterpreterMethodInfo* Interpreter::MethodHandleToInterpreterMethInfoPtr(CORINFO_METHOD_HANDLE md)
{
CONTRACTL {
NOTHROW;
GC_TRIGGERS;
} CONTRACTL_END;
// This query function will never allocate the table...
if (s_methodHandleToInterpMethInfoPtrMap == NULL)
return NULL;
// Otherwise...if we observe s_addrToMdMap non-null, the lock below must be initialized.
CrstHolder ch(&s_interpStubToMDMapLock);
MethodHandleToInterpMethInfoPtrMap* map = Interpreter::GetMethodHandleToInterpMethInfoPtrMap();
MethInfo mi;
mi.m_info = NULL;
(void)map->Lookup(md, &mi);
return mi.m_info;
}
#ifndef DACCESS_COMPILE
// Requires that the current thread holds "s_methodCacheLock."
ILOffsetToItemCache* InterpreterMethodInfo::GetCacheForCall(Object* thisArg, void* genericsCtxtArg, bool alloc)
{
// First, does the current method have dynamic generic information, and, if so,
// what kind?
CORINFO_CONTEXT_HANDLE context = GetPreciseGenericsContext(thisArg, genericsCtxtArg);
if (context == MAKE_METHODCONTEXT(m_method))
{
// No dynamic generics context information. The caching field in "m_methInfo" is the
// ILoffset->Item cache directly.
// First, ensure that it's allocated.
if (m_methodCache == NULL && alloc)
{
// Lazy init via compare-exchange.
ILOffsetToItemCache* cache = new ILOffsetToItemCache();
void* prev = InterlockedCompareExchangeT<void*>(&m_methodCache, cache, NULL);
if (prev != NULL) delete cache;
}
return reinterpret_cast<ILOffsetToItemCache*>(m_methodCache);
}
else
{
// Otherwise, it does have generic info, so find the right cache.
// First ensure that the top-level generics-context --> cache cache exists.
GenericContextToInnerCache* outerCache = reinterpret_cast<GenericContextToInnerCache*>(m_methodCache);
if (outerCache == NULL)
{
if (alloc)
{
// Lazy init via compare-exchange.
outerCache = new GenericContextToInnerCache();
void* prev = InterlockedCompareExchangeT<void*>(&m_methodCache, outerCache, NULL);
if (prev != NULL)
{
delete outerCache;
outerCache = reinterpret_cast<GenericContextToInnerCache*>(prev);
}
}
else
{
return NULL;
}
}
// Does the outerCache already have an entry for this instantiation?
ILOffsetToItemCache* innerCache = NULL;
if (!outerCache->GetItem(size_t(context), innerCache) && alloc)
{
innerCache = new ILOffsetToItemCache();
outerCache->AddItem(size_t(context), innerCache);
}
return innerCache;
}
}
void Interpreter::CacheCallInfo(unsigned iloffset, CallSiteCacheData* callInfo)
{
CrstHolder ch(&s_methodCacheLock);
ILOffsetToItemCache* cache = GetThisExecCache(true);
// Insert, but if the item is already there, delete "mdcs" (which would have been owned
// by the cache).
// (Duplicate entries can happen because of recursive calls -- F makes a recursive call to F, and when it
// returns wants to cache it, but the recursive call makes a furher recursive call, and caches that, so the
// first call finds the iloffset already occupied.)
if (!cache->AddItem(iloffset, CachedItem(callInfo)))
{
delete callInfo;
}
}
CallSiteCacheData* Interpreter::GetCachedCallInfo(unsigned iloffset)
{
CrstHolder ch(&s_methodCacheLock);
ILOffsetToItemCache* cache = GetThisExecCache(false);
if (cache == NULL) return NULL;
// Otherwise...
CachedItem item;
if (cache->GetItem(iloffset, item))
{
_ASSERTE_MSG(item.m_tag == CIK_CallSite, "Wrong cached item tag.");
return item.m_value.m_callSiteInfo;
}
else
{
return NULL;
}
}
void Interpreter::CacheInstanceField(unsigned iloffset, FieldDesc* fld)
{
CrstHolder ch(&s_methodCacheLock);
ILOffsetToItemCache* cache = GetThisExecCache(true);
cache->AddItem(iloffset, CachedItem(fld));
}
FieldDesc* Interpreter::GetCachedInstanceField(unsigned iloffset)
{
CrstHolder ch(&s_methodCacheLock);
ILOffsetToItemCache* cache = GetThisExecCache(false);
if (cache == NULL) return NULL;
// Otherwise...
CachedItem item;
if (cache->GetItem(iloffset, item))
{
_ASSERTE_MSG(item.m_tag == CIK_InstanceField, "Wrong cached item tag.");
return item.m_value.m_instanceField;
}
else
{
return NULL;
}
}
void Interpreter::CacheStaticField(unsigned iloffset, StaticFieldCacheEntry* pEntry)
{
CrstHolder ch(&s_methodCacheLock);
ILOffsetToItemCache* cache = GetThisExecCache(true);
// If (say) a concurrent thread has beaten us to this, delete the entry (which otherwise would have
// been owned by the cache).
if (!cache->AddItem(iloffset, CachedItem(pEntry)))
{
delete pEntry;
}
}
StaticFieldCacheEntry* Interpreter::GetCachedStaticField(unsigned iloffset)
{
CrstHolder ch(&s_methodCacheLock);
ILOffsetToItemCache* cache = GetThisExecCache(false);
if (cache == NULL)
return NULL;
// Otherwise...
CachedItem item;
if (cache->GetItem(iloffset, item))
{
_ASSERTE_MSG(item.m_tag == CIK_StaticField, "Wrong cached item tag.");
return item.m_value.m_staticFieldAddr;
}
else
{
return NULL;
}
}
void Interpreter::CacheClassHandle(unsigned iloffset, CORINFO_CLASS_HANDLE clsHnd)
{
CrstHolder ch(&s_methodCacheLock);
ILOffsetToItemCache* cache = GetThisExecCache(true);
cache->AddItem(iloffset, CachedItem(clsHnd));
}
CORINFO_CLASS_HANDLE Interpreter::GetCachedClassHandle(unsigned iloffset)
{
CrstHolder ch(&s_methodCacheLock);
ILOffsetToItemCache* cache = GetThisExecCache(false);
if (cache == NULL)
return NULL;
// Otherwise...
CachedItem item;
if (cache->GetItem(iloffset, item))
{
_ASSERTE_MSG(item.m_tag == CIK_ClassHandle, "Wrong cached item tag.");
return item.m_value.m_clsHnd;
}
else
{
return NULL;
}
}
#endif // DACCESS_COMPILE
// Statics
// Theses are not debug-only.
ConfigMethodSet Interpreter::s_InterpretMeths;
ConfigMethodSet Interpreter::s_InterpretMethsExclude;
ConfigDWORD Interpreter::s_InterpretMethHashMin;
ConfigDWORD Interpreter::s_InterpretMethHashMax;
ConfigDWORD Interpreter::s_InterpreterJITThreshold;
ConfigDWORD Interpreter::s_InterpreterDoLoopMethodsFlag;
ConfigDWORD Interpreter::s_InterpreterUseCachingFlag;
ConfigDWORD Interpreter::s_InterpreterLooseRulesFlag;
bool Interpreter::s_InterpreterDoLoopMethods;
bool Interpreter::s_InterpreterUseCaching;
bool Interpreter::s_InterpreterLooseRules;
CrstExplicitInit Interpreter::s_methodCacheLock;
CrstExplicitInit Interpreter::s_interpStubToMDMapLock;
// The static variables below are debug-only.
#if INTERP_TRACING
LONG Interpreter::s_totalInvocations = 0;
LONG Interpreter::s_totalInterpCalls = 0;
LONG Interpreter::s_totalInterpCallsToGetters = 0;
LONG Interpreter::s_totalInterpCallsToDeadSimpleGetters = 0;
LONG Interpreter::s_totalInterpCallsToDeadSimpleGettersShortCircuited = 0;
LONG Interpreter::s_totalInterpCallsToSetters = 0;
LONG Interpreter::s_totalInterpCallsToIntrinsics = 0;
LONG Interpreter::s_totalInterpCallsToIntrinsicsUnhandled = 0;
LONG Interpreter::s_tokenResolutionOpportunities[RTK_Count] = {0, };
LONG Interpreter::s_tokenResolutionCalls[RTK_Count] = {0, };
const char* Interpreter::s_tokenResolutionKindNames[RTK_Count] =
{
"Undefined",
"Constrained",
"NewObj",
"NewArr",
"LdToken",
"LdFtn",
"LdVirtFtn",
"SFldAddr",
"LdElem",
"Call",
"LdObj",
"StObj",
"CpObj",
"InitObj",
"IsInst",
"CastClass",
"MkRefAny",
"RefAnyVal",
"Sizeof",
"StElem",
"Box",
"Unbox",
"UnboxAny",
"LdFld",
"LdFldA",
"StFld",
"FindClass",
"Exception",
};
FILE* Interpreter::s_InterpreterLogFile = NULL;
ConfigDWORD Interpreter::s_DumpInterpreterStubsFlag;
ConfigDWORD Interpreter::s_TraceInterpreterEntriesFlag;
ConfigDWORD Interpreter::s_TraceInterpreterILFlag;
ConfigDWORD Interpreter::s_TraceInterpreterOstackFlag;
ConfigDWORD Interpreter::s_TraceInterpreterVerboseFlag;
ConfigDWORD Interpreter::s_TraceInterpreterJITTransitionFlag;
ConfigDWORD Interpreter::s_InterpreterStubMin;
ConfigDWORD Interpreter::s_InterpreterStubMax;
#endif // INTERP_TRACING
#if INTERP_ILINSTR_PROFILE
unsigned short Interpreter::s_ILInstrCategories[512];
int Interpreter::s_ILInstrExecs[256] = {0, };
int Interpreter::s_ILInstrExecsByCategory[512] = {0, };
int Interpreter::s_ILInstr2ByteExecs[Interpreter::CountIlInstr2Byte] = {0, };
#if INTERP_ILCYCLE_PROFILE
unsigned __int64 Interpreter::s_ILInstrCycles[512] = { 0, };
unsigned __int64 Interpreter::s_ILInstrCyclesByCategory[512] = { 0, };
// XXX
unsigned __int64 Interpreter::s_callCycles = 0;
unsigned Interpreter::s_calls = 0;
void Interpreter::UpdateCycleCount()
{
unsigned __int64 endCycles;
bool b = CycleTimer::GetThreadCyclesS(&endCycles); _ASSERTE(b);
if (m_instr != CEE_COUNT)
{
unsigned __int64 delta = (endCycles - m_startCycles);
if (m_exemptCycles > 0)
{
delta = delta - m_exemptCycles;
m_exemptCycles = 0;
}
CycleTimer::InterlockedAddU64(&s_ILInstrCycles[m_instr], delta);
}
// In any case, set the instruction to the current one, and record it's start time.
m_instr = (*m_ILCodePtr);
if (m_instr == CEE_PREFIX1) {
m_instr = *(m_ILCodePtr + 1) + 0x100;
}
b = CycleTimer::GetThreadCyclesS(&m_startCycles); _ASSERTE(b);
}
#endif // INTERP_ILCYCLE_PROFILE
#endif // INTERP_ILINSTR_PROFILE
#ifdef _DEBUG
InterpreterMethodInfo** Interpreter::s_interpMethInfos = NULL;
unsigned Interpreter::s_interpMethInfosAllocSize = 0;
unsigned Interpreter::s_interpMethInfosCount = 0;
bool Interpreter::TOSIsPtr()
{
if (m_curStackHt == 0)
return false;
return CorInfoTypeIsPointer(OpStackTypeGet(m_curStackHt - 1).ToCorInfoType());
}
#endif // DEBUG
ConfigDWORD Interpreter::s_PrintPostMortemFlag;
// InterpreterCache.
template<typename Key, typename Val>
InterpreterCache<Key,Val>::InterpreterCache() : m_pairs(NULL), m_allocSize(0), m_count(0)
{
#ifdef _DEBUG
AddAllocBytes(sizeof(*this));
#endif
}
#ifdef _DEBUG
// static
static unsigned InterpreterCacheAllocBytes = 0;
const unsigned KBYTE = 1024;
const unsigned MBYTE = KBYTE*KBYTE;
const unsigned InterpreterCacheAllocBytesIncrement = 16*KBYTE;
static unsigned InterpreterCacheAllocBytesNextTarget = InterpreterCacheAllocBytesIncrement;
template<typename Key, typename Val>
void InterpreterCache<Key,Val>::AddAllocBytes(unsigned bytes)
{
// Reinstate this code if you want to track bytes attributable to caching.
#if 0
InterpreterCacheAllocBytes += bytes;
if (InterpreterCacheAllocBytes > InterpreterCacheAllocBytesNextTarget)
{
printf("Total cache alloc = %d bytes.\n", InterpreterCacheAllocBytes);
fflush(stdout);
InterpreterCacheAllocBytesNextTarget += InterpreterCacheAllocBytesIncrement;
}
#endif
}
#endif // _DEBUG
template<typename Key, typename Val>
void InterpreterCache<Key,Val>::EnsureCanInsert()
{
if (m_count < m_allocSize)
return;
// Otherwise, must make room.
if (m_allocSize == 0)
{
_ASSERTE(m_count == 0);
m_pairs = new KeyValPair[InitSize];
m_allocSize = InitSize;
#ifdef _DEBUG
AddAllocBytes(m_allocSize * sizeof(KeyValPair));
#endif
}
else
{
unsigned short newSize = min(m_allocSize * 2, USHRT_MAX);
KeyValPair* newPairs = new KeyValPair[newSize];
memcpy(newPairs, m_pairs, m_count * sizeof(KeyValPair));
delete[] m_pairs;
m_pairs = newPairs;
#ifdef _DEBUG
AddAllocBytes((newSize - m_allocSize) * sizeof(KeyValPair));
#endif
m_allocSize = newSize;
}
}
template<typename Key, typename Val>
bool InterpreterCache<Key,Val>::AddItem(Key key, Val val)
{
EnsureCanInsert();
// Find the index to insert before.
unsigned firstGreaterOrEqual = 0;
for (; firstGreaterOrEqual < m_count; firstGreaterOrEqual++)
{
if (m_pairs[firstGreaterOrEqual].m_key >= key)
break;
}
if (firstGreaterOrEqual < m_count && m_pairs[firstGreaterOrEqual].m_key == key)
{
_ASSERTE(m_pairs[firstGreaterOrEqual].m_val == val);
return false;
}
// Move everything starting at firstGreater up one index (if necessary)
if (m_count > 0)
{
for (unsigned k = m_count-1; k >= firstGreaterOrEqual; k--)
{
m_pairs[k + 1] = m_pairs[k];
if (k == 0)
break;
}
}
// Now we can insert the new element.
m_pairs[firstGreaterOrEqual].m_key = key;
m_pairs[firstGreaterOrEqual].m_val = val;
m_count++;
return true;
}
template<typename Key, typename Val>
bool InterpreterCache<Key,Val>::GetItem(Key key, Val& v)
{
unsigned lo = 0;
unsigned hi = m_count;
// Invariant: we've determined that the pair for "iloffset", if present,
// is in the index interval [lo, hi).
while (lo < hi)
{
unsigned mid = (hi + lo)/2;
Key midKey = m_pairs[mid].m_key;
if (key == midKey)
{
v = m_pairs[mid].m_val;
return true;
}
else if (key < midKey)
{
hi = mid;
}
else
{
_ASSERTE(key > midKey);
lo = mid + 1;
}
}
// If we reach here without returning, it's not here.
return false;
}
// TODO: add a header comment here describing this function.
void Interpreter::OpStackNormalize()
{
size_t largeStructStackOffset = 0;
// Yes, I've written a quadratic algorithm here. I don't think it will matter in practice.
for (unsigned i = 0; i < m_curStackHt; i++)
{
InterpreterType tp = OpStackTypeGet(i);
if (tp.IsLargeStruct(&m_interpCeeInfo))
{
size_t sz = tp.Size(&m_interpCeeInfo);
void* addr = OpStackGet<void*>(i);
if (IsInLargeStructLocalArea(addr))
{
// We're going to allocate space at the top for the new value, then copy everything above the current slot
// up into that new space, then copy the value into the vacated space.
// How much will we have to copy?
size_t toCopy = m_largeStructOperandStackHt - largeStructStackOffset;
// Allocate space for the new value.
void* dummy = LargeStructOperandStackPush(sz);
// Remember where we're going to write to.
BYTE* fromAddr = m_largeStructOperandStack + largeStructStackOffset;
BYTE* toAddr = fromAddr + sz;
memcpy(toAddr, fromAddr, toCopy);
// Now copy the local variable value.
memcpy(fromAddr, addr, sz);
OpStackSet<void*>(i, fromAddr);
}
largeStructStackOffset += sz;
}
}
// When we've normalized the stack, it contains no pointers to locals.
m_orOfPushedInterpreterTypes = 0;
}
#if INTERP_TRACING
// Code copied from eeinterface.cpp in "compiler". Should be common...
static const char* CorInfoTypeNames[] = {
"undef",
"void",
"bool",
"char",
"byte",
"ubyte",
"short",
"ushort",
"int",
"uint",
"long",
"ulong",
"nativeint",
"nativeuint",
"float",
"double",
"string",
"ptr",
"byref",
"valueclass",
"class",
"refany",
"var"
};
// Also see Compiler::lookupNamedIntrinsic
Interpreter::InterpreterNamedIntrinsics Interpreter::getNamedIntrinsicID(CEEInfo* info, CORINFO_METHOD_HANDLE methodHnd)
{
InterpreterNamedIntrinsics result = NI_Illegal;
const char* namespaceName = NULL;
const char* className = NULL;
const char* methodName = getMethodName(info, (CORINFO_METHOD_HANDLE)methodHnd, &className, &namespaceName, NULL);
if (strncmp(namespaceName, "System", 6) == 0)
{
namespaceName += 6;
if (namespaceName[0] == '.')
{
namespaceName += 1;
if (strcmp(namespaceName, "StubHelpers") == 0)
{
if (strcmp(className, "StubHelpers") == 0)
{
if (strcmp(methodName, "GetStubContext") == 0)
{
result = NI_System_StubHelpers_GetStubContext;
}
}
}
else if (strncmp(namespaceName, "Runtime.", 8) == 0)
{
namespaceName += 8;
if (strcmp(namespaceName, "InteropServices") == 0)
{
if (strcmp(className, "MemoryMarshal") == 0)
{
if (strcmp(methodName, "GetArrayDataReference") == 0)
{
result = NI_System_Runtime_InteropService_MemoryMarshal_GetArrayDataReference;
}
}
}
}
}
}
return result;
}
// Simple version of getMethodName which supports IL Stubs such as IL_STUB_PInvoke additionally.
// Also see getMethodNameFromMetadata and printMethodName in corinfo.h
const char* Interpreter::getMethodName(CEEInfo* info, CORINFO_METHOD_HANDLE hnd, const char** className, const char** namespaceName, const char **enclosingClassName)
{
MethodDesc *pMD = GetMethod(hnd);
if (pMD->IsILStub())
{
if (className != NULL)
{
*className = ILStubResolver::GetStubClassName(pMD);
}
return pMD->GetName();
}
return info->getMethodNameFromMetadata(hnd, className, namespaceName, enclosingClassName);
}
const char* eeGetMethodFullName(CEEInfo* info, CORINFO_METHOD_HANDLE hnd, const char** clsName)
{
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_ANY;
} CONTRACTL_END;
GCX_PREEMP();
const char* returnType = NULL;
const char* className;
const char* methodName = Interpreter::getMethodName(info, hnd, &className);
if (clsName != NULL)
{
*clsName = className;
}
size_t length = 0;
unsigned i;
/* Generating the full signature is a two-pass process. First we have to walk
the components in order to assess the total size, then we allocate the buffer
and copy the elements into it.
*/
/* Right now there is a race-condition in the EE, className can be NULL */
/* initialize length with length of className and '.' */
if (className)
{
length = strlen(className) + 1;
}
else
{
_ASSERTE(strlen("<NULL>.") == 7);
length = 7;
}
/* add length of methodName and opening bracket */
length += strlen(methodName) + 1;
CORINFO_SIG_INFO sig;
info->getMethodSig(hnd, &sig, nullptr);
CORINFO_ARG_LIST_HANDLE argLst = sig.args;
CORINFO_CLASS_HANDLE dummyCls;
for (i = 0; i < sig.numArgs; i++)
{
CorInfoType type = strip(info->getArgType(&sig, argLst, &dummyCls));
length += strlen(CorInfoTypeNames[type]);
argLst = info->getArgNext(argLst);
}
/* add ',' if there is more than one argument */
if (sig.numArgs > 1)
{
length += (sig.numArgs - 1);
}
if (sig.retType != CORINFO_TYPE_VOID)
{
returnType = CorInfoTypeNames[sig.retType];
length += strlen(returnType) + 1; // don't forget the delimiter ':'
}
/* add closing bracket and null terminator */
length += 2;
char* retName = new char[length];
/* Now generate the full signature string in the allocated buffer */
if (className)
{
strcpy_s(retName, length, className);
strcat_s(retName, length, ":");
}
else
{
strcpy_s(retName, length, "<NULL>.");
}
strcat_s(retName, length, methodName);
// append the signature
strcat_s(retName, length, "(");
argLst = sig.args;
for (i = 0; i < sig.numArgs; i++)
{
CorInfoType type = strip(info->getArgType(&sig, argLst, &dummyCls));
strcat_s(retName, length, CorInfoTypeNames[type]);
argLst = info->getArgNext(argLst);
if (i + 1 < sig.numArgs)
{
strcat_s(retName, length, ",");
}
}
strcat_s(retName, length, ")");
if (returnType)
{
strcat_s(retName, length, ":");
strcat_s(retName, length, returnType);
}
_ASSERTE(strlen(retName) == length - 1);
return(retName);
}
const char* Interpreter::eeGetMethodFullName(CORINFO_METHOD_HANDLE hnd)
{
return ::eeGetMethodFullName(&m_interpCeeInfo, hnd);
}
const char* ILOpNames[256*2];
bool ILOpNamesInited = false;
void InitILOpNames()
{
if (!ILOpNamesInited)
{
// Initialize the array.
#define OPDEF(c,s,pop,push,args,type,l,s1,s2,ctrl) if (s1 == 0xfe || s1 == 0xff) { int ind ((unsigned(s1) << 8) + unsigned(s2)); ind -= 0xfe00; ILOpNames[ind] = s; }
#include "opcode.def"
#undef OPDEF
ILOpNamesInited = true;
}
};
const char* Interpreter::ILOp(BYTE* m_ILCodePtr)
{
InitILOpNames();
BYTE b = *m_ILCodePtr;
if (b == 0xfe)
{
return ILOpNames[*(m_ILCodePtr + 1)];
}
else
{
return ILOpNames[(0x1 << 8) + b];
}
}
const char* Interpreter::ILOp1Byte(unsigned short ilInstrVal)
{
InitILOpNames();
return ILOpNames[(0x1 << 8) + ilInstrVal];
}
const char* Interpreter::ILOp2Byte(unsigned short ilInstrVal)
{
InitILOpNames();
return ILOpNames[ilInstrVal];
}
void Interpreter::PrintOStack()
{
if (m_curStackHt == 0)
{
fprintf(GetLogFile(), " <empty>\n");
}
else
{
for (unsigned k = 0; k < m_curStackHt; k++)
{
CorInfoType cit = OpStackTypeGet(k).ToCorInfoType();
_ASSERTE(IsStackNormalType(cit));
fprintf(GetLogFile(), " %4d: %10s: ", k, CorInfoTypeNames[cit]);
PrintOStackValue(k);
fprintf(GetLogFile(), "\n");
}
}
fflush(GetLogFile());
}
void Interpreter::PrintOStackValue(unsigned index)
{
_ASSERTE_MSG(index < m_curStackHt, "precondition");
InterpreterType it = OpStackTypeGet(index);
if (it.IsLargeStruct(&m_interpCeeInfo))
{
PrintValue(it, OpStackGet<BYTE*>(index));
}
else
{
PrintValue(it, reinterpret_cast<BYTE*>(OpStackGetAddr(index, it.Size(&m_interpCeeInfo))));
}
}
void Interpreter::PrintLocals()
{
if (m_methInfo->m_numLocals == 0)
{
fprintf(GetLogFile(), " <no locals>\n");
}
else
{
for (unsigned i = 0; i < m_methInfo->m_numLocals; i++)
{
InterpreterType it = m_methInfo->m_localDescs[i].m_type;
CorInfoType cit = it.ToCorInfoType();
void* localPtr = NULL;
if (it.IsLargeStruct(&m_interpCeeInfo))
{
void* structPtr = ArgSlotEndiannessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(i)), sizeof(void**));
localPtr = *reinterpret_cast<void**>(structPtr);
}
else
{
localPtr = ArgSlotEndiannessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(i)), it.Size(&m_interpCeeInfo));
}
fprintf(GetLogFile(), " loc%-4d: %10s: ", i, CorInfoTypeNames[cit]);
PrintValue(it, reinterpret_cast<BYTE*>(localPtr));
fprintf(GetLogFile(), "\n");
}
}
fflush(GetLogFile());
}
void Interpreter::PrintArgs()
{
for (unsigned k = 0; k < m_methInfo->m_numArgs; k++)
{
CorInfoType cit = GetArgType(k).ToCorInfoType();
fprintf(GetLogFile(), " %4d: %10s: ", k, CorInfoTypeNames[cit]);
PrintArgValue(k);
fprintf(GetLogFile(), "\n");
}
fprintf(GetLogFile(), "\n");
fflush(GetLogFile());
}
void Interpreter::PrintArgValue(unsigned argNum)
{
_ASSERTE_MSG(argNum < m_methInfo->m_numArgs, "precondition");
InterpreterType it = GetArgType(argNum);
PrintValue(it, GetArgAddr(argNum));
}
// Note that this is used to print non-stack-normal values, so
// it must handle all cases.
void Interpreter::PrintValue(InterpreterType it, BYTE* valAddr)
{
switch (it.ToCorInfoType())
{
case CORINFO_TYPE_BOOL:
fprintf(GetLogFile(), "%s", ((*reinterpret_cast<INT8*>(valAddr)) ? "true" : "false"));
break;
case CORINFO_TYPE_BYTE:
fprintf(GetLogFile(), "%d", *reinterpret_cast<INT8*>(valAddr));
break;
case CORINFO_TYPE_UBYTE:
fprintf(GetLogFile(), "%u", *reinterpret_cast<UINT8*>(valAddr));
break;
case CORINFO_TYPE_SHORT:
fprintf(GetLogFile(), "%d", *reinterpret_cast<INT16*>(valAddr));
break;
case CORINFO_TYPE_USHORT: case CORINFO_TYPE_CHAR:
fprintf(GetLogFile(), "%u", *reinterpret_cast<UINT16*>(valAddr));
break;
case CORINFO_TYPE_INT:
fprintf(GetLogFile(), "%d", *reinterpret_cast<INT32*>(valAddr));
break;
case CORINFO_TYPE_UINT:
fprintf(GetLogFile(), "%u", *reinterpret_cast<UINT32*>(valAddr));
break;
case CORINFO_TYPE_NATIVEINT:
{
INT64 val = static_cast<INT64>(*reinterpret_cast<NativeInt*>(valAddr));
fprintf(GetLogFile(), "%lld (= 0x%llx)", val, val);
}
break;
case CORINFO_TYPE_NATIVEUINT:
{
UINT64 val = static_cast<UINT64>(*reinterpret_cast<NativeUInt*>(valAddr));
fprintf(GetLogFile(), "%lld (= 0x%llx)", val, val);
}
break;
case CORINFO_TYPE_BYREF:
fprintf(GetLogFile(), "0x%p", *reinterpret_cast<void**>(valAddr));
break;
case CORINFO_TYPE_LONG:
{
INT64 val = *reinterpret_cast<INT64*>(valAddr);
fprintf(GetLogFile(), "%lld (= 0x%llx)", val, val);
}
break;
case CORINFO_TYPE_ULONG:
fprintf(GetLogFile(), "%lld", *reinterpret_cast<UINT64*>(valAddr));
break;
case CORINFO_TYPE_CLASS:
{
Object* obj = *reinterpret_cast<Object**>(valAddr);
if (obj == NULL)
{
fprintf(GetLogFile(), "null");
}
else
{
#ifdef _DEBUG
fprintf(GetLogFile(), "0x%p (%s) [", obj, obj->GetMethodTable()->GetDebugClassName());
#else
fprintf(GetLogFile(), "0x%p (MT=0x%p) [", obj, obj->GetMethodTable());
#endif
unsigned sz = obj->GetMethodTable()->GetBaseSize();
BYTE* objBytes = reinterpret_cast<BYTE*>(obj);
for (unsigned i = 0; i < sz; i++)
{
if (i > 0)
{
fprintf(GetLogFile(), " ");
}
fprintf(GetLogFile(), "0x%x", objBytes[i]);
}
fprintf(GetLogFile(), "]");
}
}
break;
case CORINFO_TYPE_VALUECLASS:
{
GCX_PREEMP();
fprintf(GetLogFile(), "<%s>: [", m_interpCeeInfo.getClassNameFromMetadata(it.ToClassHandle(), NULL));
unsigned sz = getClassSize(it.ToClassHandle());
for (unsigned i = 0; i < sz; i++)
{
if (i > 0)
{
fprintf(GetLogFile(), " ");
}
fprintf(GetLogFile(), "0x%02x", valAddr[i]);
}
fprintf(GetLogFile(), "]");
}
break;
case CORINFO_TYPE_REFANY:
fprintf(GetLogFile(), "<refany>");
break;
case CORINFO_TYPE_FLOAT:
fprintf(GetLogFile(), "%f", *reinterpret_cast<float*>(valAddr));
break;
case CORINFO_TYPE_DOUBLE:
fprintf(GetLogFile(), "%g", *reinterpret_cast<double*>(valAddr));
break;
case CORINFO_TYPE_PTR:
fprintf(GetLogFile(), "0x%p", *reinterpret_cast<void**>(valAddr));
break;
default:
_ASSERTE_MSG(false, "Unknown type in PrintValue.");
break;
}
}
#endif // INTERP_TRACING
#ifdef _DEBUG
void Interpreter::AddInterpMethInfo(InterpreterMethodInfo* methInfo)
{
typedef InterpreterMethodInfo* InterpreterMethodInfoPtr;
// TODO: this requires synchronization.
const unsigned InitSize = 128;
if (s_interpMethInfos == NULL)
{
s_interpMethInfos = new InterpreterMethodInfoPtr[InitSize];
s_interpMethInfosAllocSize = InitSize;
}
if (s_interpMethInfosAllocSize == s_interpMethInfosCount)
{
unsigned newSize = s_interpMethInfosAllocSize * 2;
InterpreterMethodInfoPtr* tmp = new InterpreterMethodInfoPtr[newSize];
memcpy(tmp, s_interpMethInfos, s_interpMethInfosCount * sizeof(InterpreterMethodInfoPtr));
delete[] s_interpMethInfos;
s_interpMethInfos = tmp;
s_interpMethInfosAllocSize = newSize;
}
s_interpMethInfos[s_interpMethInfosCount] = methInfo;
s_interpMethInfosCount++;
}
int _cdecl Interpreter::CompareMethInfosByInvocations(const void* mi0in, const void* mi1in)
{
const InterpreterMethodInfo* mi0 = *((const InterpreterMethodInfo**)mi0in);
const InterpreterMethodInfo* mi1 = *((const InterpreterMethodInfo**)mi1in);
if (mi0->m_invocations < mi1->m_invocations)
{
return -1;
}
else if (mi0->m_invocations == mi1->m_invocations)
{
return 0;
}
else
{
_ASSERTE(mi0->m_invocations > mi1->m_invocations);
return 1;
}
}
#if INTERP_PROFILE
int _cdecl Interpreter::CompareMethInfosByILInstrs(const void* mi0in, const void* mi1in)
{
const InterpreterMethodInfo* mi0 = *((const InterpreterMethodInfo**)mi0in);
const InterpreterMethodInfo* mi1 = *((const InterpreterMethodInfo**)mi1in);
if (mi0->m_totIlInstructionsExeced < mi1->m_totIlInstructionsExeced) return 1;
else if (mi0->m_totIlInstructionsExeced == mi1->m_totIlInstructionsExeced) return 0;
else
{
_ASSERTE(mi0->m_totIlInstructionsExeced > mi1->m_totIlInstructionsExeced);
return -1;
}
}
#endif // INTERP_PROFILE
#endif // _DEBUG
const int MIL = 1000000;
// Leaving this disabled for now.
#if 0
unsigned __int64 ForceSigWalkCycles = 0;
#endif
void Interpreter::PrintPostMortemData()
{
if (s_PrintPostMortemFlag.val(CLRConfig::INTERNAL_InterpreterPrintPostMortem) == 0)
return;
// Otherwise...
#if INTERP_TRACING
// Let's print two things: the number of methods that are 0-10, or more, and
// For each 10% of methods, cumulative % of invocations they represent. By 1% for last 10%.
// First one doesn't require any sorting.
const unsigned HistoMax = 11;
unsigned histo[HistoMax];
unsigned numExecs[HistoMax];
for (unsigned k = 0; k < HistoMax; k++)
{
histo[k] = 0; numExecs[k] = 0;
}
for (unsigned k = 0; k < s_interpMethInfosCount; k++)
{
unsigned invokes = s_interpMethInfos[k]->m_invocations;
if (invokes > HistoMax - 1)
{
invokes = HistoMax - 1;
}
histo[invokes]++;
numExecs[invokes] += s_interpMethInfos[k]->m_invocations;
}
fprintf(GetLogFile(), "Histogram of method executions:\n");
fprintf(GetLogFile(), " # of execs | # meths (%%) | cum %% | %% cum execs\n");
fprintf(GetLogFile(), " -------------------------------------------------------\n");
float fTotMeths = float(s_interpMethInfosCount);
float fTotExecs = float(s_totalInvocations);
float numPct = 0.0f;
float numExecPct = 0.0f;
for (unsigned k = 0; k < HistoMax; k++)
{
fprintf(GetLogFile(), " %10d", k);
if (k == HistoMax)
{
fprintf(GetLogFile(), "+ ");
}
else
{
fprintf(GetLogFile(), " ");
}
float pct = float(histo[k])*100.0f/fTotMeths;
numPct += pct;
float execPct = float(numExecs[k])*100.0f/fTotExecs;
numExecPct += execPct;
fprintf(GetLogFile(), "| %7d (%5.2f%%) | %6.2f%% | %6.2f%%\n", histo[k], pct, numPct, numExecPct);
}
// This sorts them in ascending order of number of invocations.
qsort(&s_interpMethInfos[0], s_interpMethInfosCount, sizeof(InterpreterMethodInfo*), &CompareMethInfosByInvocations);
fprintf(GetLogFile(), "\nFor methods sorted in ascending # of executions order, cumulative %% of executions:\n");
if (s_totalInvocations > 0)
{
fprintf(GetLogFile(), " %% of methods | max execs | cum %% of execs\n");
fprintf(GetLogFile(), " ------------------------------------------\n");
unsigned methNum = 0;
unsigned nNumExecs = 0;
float totExecsF = float(s_totalInvocations);
for (unsigned k = 10; k < 100; k += 10)
{
unsigned targ = unsigned((float(k)/100.0f)*float(s_interpMethInfosCount));
unsigned targLess1 = (targ > 0 ? targ - 1 : 0);
while (methNum < targ)
{
nNumExecs += s_interpMethInfos[methNum]->m_invocations;
methNum++;
}
float pctExecs = float(nNumExecs) * 100.0f / totExecsF;
fprintf(GetLogFile(), " %8d%% | %9d | %8.2f%%\n", k, s_interpMethInfos[targLess1]->m_invocations, pctExecs);
if (k == 90)
{
k++;
for (; k < 100; k++)
{
unsigned targ = unsigned((float(k)/100.0f)*float(s_interpMethInfosCount));
while (methNum < targ)
{
nNumExecs += s_interpMethInfos[methNum]->m_invocations;
methNum++;
}
pctExecs = float(nNumExecs) * 100.0f / totExecsF;
fprintf(GetLogFile(), " %8d%% | %9d | %8.2f%%\n", k, s_interpMethInfos[targLess1]->m_invocations, pctExecs);
}
// Now do 100%.
targ = s_interpMethInfosCount;
while (methNum < targ)
{
nNumExecs += s_interpMethInfos[methNum]->m_invocations;
methNum++;
}
pctExecs = float(nNumExecs) * 100.0f / totExecsF;
fprintf(GetLogFile(), " %8d%% | %9d | %8.2f%%\n", k, s_interpMethInfos[targLess1]->m_invocations, pctExecs);
}
}
}
fprintf(GetLogFile(), "\nTotal number of calls from interpreted code: %d.\n", s_totalInterpCalls);
fprintf(GetLogFile(), " Also, %d are intrinsics; %d of these are not currently handled intrinsically.\n",
s_totalInterpCallsToIntrinsics, s_totalInterpCallsToIntrinsicsUnhandled);
fprintf(GetLogFile(), " Of these, %d to potential property getters (%d of these dead simple), %d to setters.\n",
s_totalInterpCallsToGetters, s_totalInterpCallsToDeadSimpleGetters, s_totalInterpCallsToSetters);
fprintf(GetLogFile(), " Of the dead simple getter calls, %d have been short-circuited.\n",
s_totalInterpCallsToDeadSimpleGettersShortCircuited);
fprintf(GetLogFile(), "\nToken resolutions by category:\n");
fprintf(GetLogFile(), "Category | opportunities | calls | %%\n");
fprintf(GetLogFile(), "---------------------------------------------------\n");
for (unsigned i = RTK_Undefined; i < RTK_Count; i++)
{
float pct = 0.0;
if (s_tokenResolutionOpportunities[i] > 0)
pct = 100.0f * float(s_tokenResolutionCalls[i]) / float(s_tokenResolutionOpportunities[i]);
fprintf(GetLogFile(), "%12s | %15d | %9d | %6.2f%%\n",
s_tokenResolutionKindNames[i], s_tokenResolutionOpportunities[i], s_tokenResolutionCalls[i], pct);
}
#if INTERP_PROFILE
fprintf(GetLogFile(), "Information on num of execs:\n");
UINT64 totILInstrs = 0;
for (unsigned i = 0; i < s_interpMethInfosCount; i++) totILInstrs += s_interpMethInfos[i]->m_totIlInstructionsExeced;
float totILInstrsF = float(totILInstrs);
fprintf(GetLogFile(), "\nTotal instructions = %lld.\n", totILInstrs);
fprintf(GetLogFile(), "\nTop <=10 methods by # of IL instructions executed.\n");
fprintf(GetLogFile(), "%10s | %9s | %10s | %10s | %8s | %s\n", "tot execs", "# invokes", "code size", "ratio", "% of tot", "Method");
fprintf(GetLogFile(), "----------------------------------------------------------------------------\n");
qsort(&s_interpMethInfos[0], s_interpMethInfosCount, sizeof(InterpreterMethodInfo*), &CompareMethInfosByILInstrs);
for (unsigned i = 0; i < min(10, s_interpMethInfosCount); i++)
{
unsigned ilCodeSize = unsigned(s_interpMethInfos[i]->m_ILCodeEnd - s_interpMethInfos[i]->m_ILCode);
fprintf(GetLogFile(), "%10lld | %9d | %10d | %10.2f | %8.2f%% | %s:%s\n",
s_interpMethInfos[i]->m_totIlInstructionsExeced,
s_interpMethInfos[i]->m_invocations,
ilCodeSize,
float(s_interpMethInfos[i]->m_totIlInstructionsExeced) / float(ilCodeSize),
float(s_interpMethInfos[i]->m_totIlInstructionsExeced) * 100.0f / totILInstrsF,
s_interpMethInfos[i]->m_clsName,
s_interpMethInfos[i]->m_methName);
}
#endif // INTERP_PROFILE
#endif // _DEBUG
#if INTERP_ILINSTR_PROFILE
fprintf(GetLogFile(), "\nIL instruction profiling:\n");
// First, classify by categories.
unsigned totInstrs = 0;
#if INTERP_ILCYCLE_PROFILE
unsigned __int64 totCycles = 0;
unsigned __int64 perMeasurementOverhead = CycleTimer::QueryOverhead();
#endif // INTERP_ILCYCLE_PROFILE
for (unsigned i = 0; i < 256; i++)
{
s_ILInstrExecsByCategory[s_ILInstrCategories[i]] += s_ILInstrExecs[i];
totInstrs += s_ILInstrExecs[i];
#if INTERP_ILCYCLE_PROFILE
unsigned __int64 cycles = s_ILInstrCycles[i];
if (cycles > s_ILInstrExecs[i] * perMeasurementOverhead) cycles -= s_ILInstrExecs[i] * perMeasurementOverhead;
else cycles = 0;
s_ILInstrCycles[i] = cycles;
s_ILInstrCyclesByCategory[s_ILInstrCategories[i]] += cycles;
totCycles += cycles;
#endif // INTERP_ILCYCLE_PROFILE
}
unsigned totInstrs2Byte = 0;
#if INTERP_ILCYCLE_PROFILE
unsigned __int64 totCycles2Byte = 0;
#endif // INTERP_ILCYCLE_PROFILE
for (unsigned i = 0; i < CountIlInstr2Byte; i++)
{
unsigned ind = 0x100 + i;
s_ILInstrExecsByCategory[s_ILInstrCategories[ind]] += s_ILInstr2ByteExecs[i];
totInstrs += s_ILInstr2ByteExecs[i];
totInstrs2Byte += s_ILInstr2ByteExecs[i];
#if INTERP_ILCYCLE_PROFILE
unsigned __int64 cycles = s_ILInstrCycles[ind];
if (cycles > s_ILInstrExecs[ind] * perMeasurementOverhead) cycles -= s_ILInstrExecs[ind] * perMeasurementOverhead;
else cycles = 0;
s_ILInstrCycles[i] = cycles;
s_ILInstrCyclesByCategory[s_ILInstrCategories[ind]] += cycles;
totCycles += cycles;
totCycles2Byte += cycles;
#endif // INTERP_ILCYCLE_PROFILE
}
// Now sort the categories by # of occurrences.
InstrExecRecord ieps[256 + CountIlInstr2Byte];
for (unsigned short i = 0; i < 256; i++)
{
ieps[i].m_instr = i; ieps[i].m_is2byte = false; ieps[i].m_execs = s_ILInstrExecs[i];
#if INTERP_ILCYCLE_PROFILE
if (i == CEE_BREAK)
{
ieps[i].m_cycles = 0;
continue; // Don't count these if they occur...
}
ieps[i].m_cycles = s_ILInstrCycles[i];
_ASSERTE((ieps[i].m_execs != 0) || (ieps[i].m_cycles == 0)); // Cycles can be zero for non-zero execs because of measurement correction.
#endif // INTERP_ILCYCLE_PROFILE
}
for (unsigned short i = 0; i < CountIlInstr2Byte; i++)
{
int ind = 256 + i;
ieps[ind].m_instr = i; ieps[ind].m_is2byte = true; ieps[ind].m_execs = s_ILInstr2ByteExecs[i];
#if INTERP_ILCYCLE_PROFILE
ieps[ind].m_cycles = s_ILInstrCycles[ind];
_ASSERTE((ieps[i].m_execs != 0) || (ieps[i].m_cycles == 0)); // Cycles can be zero for non-zero execs because of measurement correction.
#endif // INTERP_ILCYCLE_PROFILE
}
qsort(&ieps[0], 256 + CountIlInstr2Byte, sizeof(InstrExecRecord), &InstrExecRecord::Compare);
fprintf(GetLogFile(), "\nInstructions (%d total, %d 1-byte):\n", totInstrs, totInstrs - totInstrs2Byte);
#if INTERP_ILCYCLE_PROFILE
if (s_callCycles > s_calls * perMeasurementOverhead) s_callCycles -= s_calls * perMeasurementOverhead;
else s_callCycles = 0;
fprintf(GetLogFile(), " MCycles (%lld total, %lld 1-byte, %lld calls (%d calls, %10.2f cyc/call):\n",
totCycles/MIL, (totCycles - totCycles2Byte)/MIL, s_callCycles/MIL, s_calls, float(s_callCycles)/float(s_calls));
#if 0
extern unsigned __int64 MetaSigCtor1Cycles;
fprintf(GetLogFile(), " MetaSig(MethodDesc, TypeHandle) ctor: %lld MCycles.\n",
MetaSigCtor1Cycles/MIL);
fprintf(GetLogFile(), " ForceSigWalk: %lld MCycles.\n",
ForceSigWalkCycles/MIL);
#endif
#endif // INTERP_ILCYCLE_PROFILE
PrintILProfile(&ieps[0], totInstrs
#if INTERP_ILCYCLE_PROFILE
, totCycles
#endif // INTERP_ILCYCLE_PROFILE
);
fprintf(GetLogFile(), "\nInstructions grouped by category: (%d total, %d 1-byte):\n", totInstrs, totInstrs - totInstrs2Byte);
#if INTERP_ILCYCLE_PROFILE
fprintf(GetLogFile(), " MCycles (%lld total, %lld 1-byte):\n",
totCycles/MIL, (totCycles - totCycles2Byte)/MIL);
#endif // INTERP_ILCYCLE_PROFILE
for (unsigned short i = 0; i < 256 + CountIlInstr2Byte; i++)
{
if (i < 256)
{
ieps[i].m_instr = i; ieps[i].m_is2byte = false;
}
else
{
ieps[i].m_instr = i - 256; ieps[i].m_is2byte = true;
}
ieps[i].m_execs = s_ILInstrExecsByCategory[i];
#if INTERP_ILCYCLE_PROFILE
ieps[i].m_cycles = s_ILInstrCyclesByCategory[i];
#endif // INTERP_ILCYCLE_PROFILE
}
qsort(&ieps[0], 256 + CountIlInstr2Byte, sizeof(InstrExecRecord), &InstrExecRecord::Compare);
PrintILProfile(&ieps[0], totInstrs
#if INTERP_ILCYCLE_PROFILE
, totCycles
#endif // INTERP_ILCYCLE_PROFILE
);
#if 0
// Early debugging code.
fprintf(GetLogFile(), "\nInstructions grouped category mapping:\n", totInstrs, totInstrs - totInstrs2Byte);
for (unsigned short i = 0; i < 256; i++)
{
unsigned short cat = s_ILInstrCategories[i];
if (cat < 256) {
fprintf(GetLogFile(), "Instr: %12s ==> %12s.\n", ILOp1Byte(i), ILOp1Byte(cat));
} else {
fprintf(GetLogFile(), "Instr: %12s ==> %12s.\n", ILOp1Byte(i), ILOp2Byte(cat - 256));
}
}
for (unsigned short i = 0; i < CountIlInstr2Byte; i++)
{
unsigned ind = 256 + i;
unsigned short cat = s_ILInstrCategories[ind];
if (cat < 256) {
fprintf(GetLogFile(), "Instr: %12s ==> %12s.\n", ILOp2Byte(i), ILOp1Byte(cat));
} else {
fprintf(GetLogFile(), "Instr: %12s ==> %12s.\n", ILOp2Byte(i), ILOp2Byte(cat - 256));
}
}
#endif
#endif // INTERP_ILINSTR_PROFILE
}
#if INTERP_ILINSTR_PROFILE
const int K = 1000;
// static
void Interpreter::PrintILProfile(Interpreter::InstrExecRecord *recs, unsigned int totInstrs
#if INTERP_ILCYCLE_PROFILE
, unsigned __int64 totCycles
#endif // INTERP_ILCYCLE_PROFILE
)
{
float fTotInstrs = float(totInstrs);
fprintf(GetLogFile(), "Instruction | execs | %% | cum %%");
#if INTERP_ILCYCLE_PROFILE
float fTotCycles = float(totCycles);
fprintf(GetLogFile(), "| KCycles | %% | cum %% | cyc/inst\n");
fprintf(GetLogFile(), "--------------------------------------------------"
"-----------------------------------------\n");
#else
fprintf(GetLogFile(), "\n-------------------------------------------\n");
#endif
float numPct = 0.0f;
#if INTERP_ILCYCLE_PROFILE
float numCyclePct = 0.0f;
#endif // INTERP_ILCYCLE_PROFILE
for (unsigned i = 0; i < 256 + CountIlInstr2Byte; i++)
{
float pct = 0.0f;
if (totInstrs > 0) pct = float(recs[i].m_execs) * 100.0f / fTotInstrs;
numPct += pct;
if (recs[i].m_execs > 0)
{
fprintf(GetLogFile(), "%12s | %9d | %6.2f%% | %6.2f%%",
(recs[i].m_is2byte ? ILOp2Byte(recs[i].m_instr) : ILOp1Byte(recs[i].m_instr)), recs[i].m_execs,
pct, numPct);
#if INTERP_ILCYCLE_PROFILE
pct = 0.0f;
if (totCycles > 0) pct = float(recs[i].m_cycles) * 100.0f / fTotCycles;
numCyclePct += pct;
float cyclesPerInst = float(recs[i].m_cycles) / float(recs[i].m_execs);
fprintf(GetLogFile(), "| %12llu | %6.2f%% | %6.2f%% | %11.2f",
recs[i].m_cycles/K, pct, numCyclePct, cyclesPerInst);
#endif // INTERP_ILCYCLE_PROFILE
fprintf(GetLogFile(), "\n");
}
}
}
#endif // INTERP_ILINSTR_PROFILE
#endif // FEATURE_INTERPRETER