mirror of
https://github.com/VSadov/Satori.git
synced 2025-06-11 18:20:26 +09:00

* 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
12572 lines
421 KiB
C++
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
|