in src/coreclr/jit/fgbasic.cpp [1087:2747]
void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, FixedBitVect* jumpTarget)
{
const BYTE* codeBegp = codeAddr;
const BYTE* codeEndp = codeAddr + codeSize;
unsigned varNum;
var_types varType = DUMMY_INIT(TYP_UNDEF); // TYP_ type
bool typeIsNormed = false;
FgStack pushedStack;
const bool isForceInline = (info.compFlags & CORINFO_FLG_FORCEINLINE) != 0;
const bool isInlining = compIsForInlining();
unsigned retBlocks = 0;
int prefixFlags = 0;
bool preciseScan = makeInlineObservations && compInlineResult->GetPolicy()->RequiresPreciseScan();
const bool resolveTokens = preciseScan;
// Track offsets where IL instructions begin in DEBUG builds. Used to
// validate debug info generated by the JIT.
assert(codeSize == compInlineContext->GetILSize());
INDEBUG(FixedBitVect* ilInstsSet = FixedBitVect::bitVectInit(codeSize, this));
if (makeInlineObservations)
{
// Set default values for profile (to avoid NoteFailed in CALLEE_IL_CODE_SIZE's handler)
// these will be overridden later.
compInlineResult->NoteBool(InlineObservation::CALLSITE_HAS_PROFILE_WEIGHTS, true);
compInlineResult->NoteDouble(InlineObservation::CALLSITE_PROFILE_FREQUENCY, 1.0);
// Observe force inline state and code size.
compInlineResult->NoteBool(InlineObservation::CALLEE_IS_FORCE_INLINE, isForceInline);
compInlineResult->NoteInt(InlineObservation::CALLEE_IL_CODE_SIZE, codeSize);
// Determine if call site is within a try.
if (isInlining && impInlineInfo->iciBlock->hasTryIndex())
{
compInlineResult->Note(InlineObservation::CALLSITE_IN_TRY_REGION);
}
// Determine if the call site is in a no-return block
if (isInlining && impInlineInfo->iciBlock->KindIs(BBJ_THROW))
{
compInlineResult->Note(InlineObservation::CALLSITE_IN_NORETURN_REGION);
}
// Determine if the call site is in a loop.
if (isInlining && impInlineInfo->iciBlock->HasFlag(BBF_BACKWARD_JUMP))
{
compInlineResult->Note(InlineObservation::CALLSITE_IN_LOOP);
}
#ifdef DEBUG
// If inlining, this method should still be a candidate.
if (isInlining)
{
assert(compInlineResult->IsCandidate());
}
#endif // DEBUG
// note that we're starting to look at the opcodes.
compInlineResult->Note(InlineObservation::CALLEE_BEGIN_OPCODE_SCAN);
}
CORINFO_RESOLVED_TOKEN resolvedToken;
OPCODE opcode = CEE_NOP;
OPCODE prevOpcode = CEE_NOP;
bool handled = false;
while (codeAddr < codeEndp)
{
prevOpcode = opcode;
opcode = (OPCODE)getU1LittleEndian(codeAddr);
INDEBUG(ilInstsSet->bitVectSet((UINT)(codeAddr - codeBegp)));
codeAddr += sizeof(int8_t);
if (!handled && preciseScan)
{
// Push something unknown to the stack since we couldn't find anything useful for inlining
pushedStack.PushUnknown();
}
handled = false;
DECODE_OPCODE:
if ((unsigned)opcode >= CEE_COUNT)
{
BADCODE3("Illegal opcode", ": %02X", (int)opcode);
}
if ((opcode >= CEE_LDARG_0 && opcode <= CEE_STLOC_S) || (opcode >= CEE_LDARG && opcode <= CEE_STLOC))
{
opts.lvRefCount++;
}
if (makeInlineObservations && (opcode >= CEE_LDNULL) && (opcode <= CEE_LDC_R8))
{
// LDTOKEN and LDSTR are handled below
pushedStack.PushConstant();
handled = true;
}
unsigned sz = opcodeSizes[opcode];
switch (opcode)
{
case CEE_PREFIX1:
{
if (codeAddr >= codeEndp)
{
goto TOO_FAR;
}
opcode = (OPCODE)(256 + getU1LittleEndian(codeAddr));
codeAddr += sizeof(int8_t);
goto DECODE_OPCODE;
}
case CEE_PREFIX2:
case CEE_PREFIX3:
case CEE_PREFIX4:
case CEE_PREFIX5:
case CEE_PREFIX6:
case CEE_PREFIX7:
case CEE_PREFIXREF:
{
BADCODE3("Illegal opcode", ": %02X", (int)opcode);
}
case CEE_SIZEOF:
case CEE_LDTOKEN:
case CEE_LDSTR:
{
if (preciseScan)
{
pushedStack.PushConstant();
handled = true;
}
break;
}
case CEE_DUP:
{
if (preciseScan)
{
pushedStack.Push(pushedStack.Top());
handled = true;
}
break;
}
case CEE_THROW:
{
if (makeInlineObservations)
{
compInlineResult->Note(InlineObservation::CALLEE_THROW_BLOCK);
}
break;
}
case CEE_BOX:
{
if (makeInlineObservations)
{
int toSkip =
impBoxPatternMatch(nullptr, codeAddr + sz, codeEndp, BoxPatterns::MakeInlineObservation);
if (toSkip > 0)
{
// toSkip > 0 means we most likely will hit a pattern (e.g. box+isinst+brtrue) that
// will be folded into a const
if (preciseScan)
{
codeAddr += toSkip;
}
}
}
break;
}
case CEE_CASTCLASS:
case CEE_ISINST:
{
if (makeInlineObservations)
{
FgStack::FgSlot slot = pushedStack.Top();
if (FgStack::IsConstantOrConstArg(slot, impInlineInfo) ||
FgStack::IsExactArgument(slot, impInlineInfo))
{
compInlineResult->Note(InlineObservation::CALLSITE_FOLDABLE_EXPR_UN);
handled = true; // and keep argument in the pushedStack
}
else if (FgStack::IsArgument(slot))
{
compInlineResult->Note(InlineObservation::CALLEE_ARG_FEEDS_CAST);
handled = true; // and keep argument in the pushedStack
}
}
break;
}
case CEE_CALL:
case CEE_CALLVIRT:
{
// There has to be code after the call, otherwise the inlinee is unverifiable.
if (isInlining)
{
noway_assert(codeAddr < codeEndp - sz);
}
if (!makeInlineObservations)
{
break;
}
CORINFO_METHOD_HANDLE methodHnd = nullptr;
bool isIntrinsic = false;
NamedIntrinsic ni = NI_Illegal;
if (resolveTokens)
{
impResolveToken(codeAddr, &resolvedToken, CORINFO_TOKENKIND_Method);
methodHnd = resolvedToken.hMethod;
isIntrinsic = eeIsIntrinsic(methodHnd);
}
if (isIntrinsic)
{
ni = lookupNamedIntrinsic(methodHnd);
bool foldableIntrinsic = false;
if (IsMathIntrinsic(ni))
{
// Most Math(F) intrinsics have single arguments
foldableIntrinsic = FgStack::IsConstantOrConstArg(pushedStack.Top(), impInlineInfo);
}
else
{
switch (ni)
{
// These are most likely foldable without arguments
case NI_System_Collections_Generic_Comparer_get_Default:
case NI_System_Collections_Generic_EqualityComparer_get_Default:
case NI_System_Enum_HasFlag:
case NI_System_GC_KeepAlive:
{
pushedStack.PushUnknown();
foldableIntrinsic = true;
break;
}
case NI_System_SpanHelpers_ClearWithoutReferences:
case NI_System_SpanHelpers_Fill:
case NI_System_SpanHelpers_SequenceEqual:
case NI_System_SpanHelpers_Memmove:
{
if (FgStack::IsConstArgument(pushedStack.Top(), impInlineInfo))
{
// Constant (at its call-site) argument feeds the Memmove/Memcmp length argument.
// We most likely will be able to unroll it.
// It is important to only raise this hint for constant arguments, if it's just a
// constant in the inlinee itself then we don't need to inline it for unrolling.
compInlineResult->Note(InlineObservation::CALLSITE_UNROLLABLE_MEMOP);
}
break;
}
case NI_System_Span_get_Item:
case NI_System_ReadOnlySpan_get_Item:
{
if (FgStack::IsArgument(pushedStack.Top(0)) || FgStack::IsArgument(pushedStack.Top(1)))
{
compInlineResult->Note(InlineObservation::CALLEE_ARG_FEEDS_RANGE_CHECK);
}
break;
}
case NI_System_Runtime_CompilerServices_RuntimeHelpers_IsKnownConstant:
if (FgStack::IsConstArgument(pushedStack.Top(), impInlineInfo))
{
compInlineResult->Note(InlineObservation::CALLEE_CONST_ARG_FEEDS_ISCONST);
}
else
{
compInlineResult->Note(InlineObservation::CALLEE_ARG_FEEDS_ISCONST);
}
// RuntimeHelpers.IsKnownConstant is always folded into a const
pushedStack.PushConstant();
foldableIntrinsic = true;
break;
// These are foldable if the first argument is a constant
case NI_PRIMITIVE_LeadingZeroCount:
case NI_PRIMITIVE_Log2:
case NI_PRIMITIVE_PopCount:
case NI_PRIMITIVE_TrailingZeroCount:
case NI_System_Type_get_IsEnum:
case NI_System_Type_GetEnumUnderlyingType:
case NI_System_Type_get_IsValueType:
case NI_System_Type_get_IsPrimitive:
case NI_System_Type_get_IsByRefLike:
case NI_System_Type_get_IsGenericType:
case NI_System_Type_GetTypeFromHandle:
case NI_System_String_get_Length:
case NI_System_Buffers_Binary_BinaryPrimitives_ReverseEndianness:
#if defined(FEATURE_HW_INTRINSICS)
#if defined(TARGET_ARM64)
case NI_ArmBase_Arm64_LeadingZeroCount:
case NI_ArmBase_Arm64_ReverseElementBits:
case NI_ArmBase_LeadingZeroCount:
case NI_ArmBase_ReverseElementBits:
case NI_Vector64_Create:
case NI_Vector64_CreateScalar:
case NI_Vector64_CreateScalarUnsafe:
#endif // TARGET_ARM64
case NI_Vector128_Create:
case NI_Vector128_CreateScalar:
case NI_Vector128_CreateScalarUnsafe:
case NI_VectorT_Create:
#if defined(TARGET_XARCH)
case NI_BMI1_TrailingZeroCount:
case NI_BMI1_X64_TrailingZeroCount:
case NI_LZCNT_LeadingZeroCount:
case NI_LZCNT_X64_LeadingZeroCount:
case NI_POPCNT_PopCount:
case NI_POPCNT_X64_PopCount:
case NI_Vector256_Create:
case NI_Vector512_Create:
case NI_Vector256_CreateScalar:
case NI_Vector512_CreateScalar:
case NI_Vector256_CreateScalarUnsafe:
case NI_Vector512_CreateScalarUnsafe:
case NI_X86Base_BitScanForward:
case NI_X86Base_X64_BitScanForward:
case NI_X86Base_BitScanReverse:
case NI_X86Base_X64_BitScanReverse:
#endif // TARGET_XARCH
#endif // FEATURE_HW_INTRINSICS
{
// Top() in order to keep it as is in case of foldableIntrinsic
if (FgStack::IsConstantOrConstArg(pushedStack.Top(), impInlineInfo))
{
foldableIntrinsic = true;
}
break;
}
// These are foldable if two arguments are constants
case NI_PRIMITIVE_RotateLeft:
case NI_PRIMITIVE_RotateRight:
case NI_System_Type_op_Equality:
case NI_System_Type_op_Inequality:
case NI_System_String_get_Chars:
case NI_System_Type_IsAssignableTo:
case NI_System_Type_IsAssignableFrom:
{
if (FgStack::IsConstantOrConstArg(pushedStack.Top(0), impInlineInfo) &&
FgStack::IsConstantOrConstArg(pushedStack.Top(1), impInlineInfo))
{
foldableIntrinsic = true;
pushedStack.PushConstant();
}
break;
}
case NI_IsSupported_True:
case NI_IsSupported_False:
case NI_IsSupported_Type:
{
foldableIntrinsic = true;
pushedStack.PushConstant();
break;
}
case NI_Vector_GetCount:
{
foldableIntrinsic = true;
pushedStack.PushConstant();
// TODO: for FEATURE_SIMD check if it's a loop condition - we unroll such loops.
break;
}
case NI_SRCS_UNSAFE_Add:
case NI_SRCS_UNSAFE_AddByteOffset:
case NI_SRCS_UNSAFE_AreSame:
case NI_SRCS_UNSAFE_ByteOffset:
case NI_SRCS_UNSAFE_IsAddressGreaterThan:
case NI_SRCS_UNSAFE_IsAddressLessThan:
case NI_SRCS_UNSAFE_IsNullRef:
case NI_SRCS_UNSAFE_Subtract:
case NI_SRCS_UNSAFE_SubtractByteOffset:
{
// These are effectively primitive binary operations so the
// handling roughly mirrors the handling for CEE_ADD and
// friends that exists elsewhere in this method
if (!preciseScan)
{
switch (ni)
{
case NI_SRCS_UNSAFE_AreSame:
case NI_SRCS_UNSAFE_IsAddressGreaterThan:
case NI_SRCS_UNSAFE_IsAddressLessThan:
case NI_SRCS_UNSAFE_IsNullRef:
{
fgObserveInlineConstants(opcode, pushedStack, isInlining);
break;
}
default:
{
break;
}
}
}
else
{
// Unlike the normal binary operation handling, this is an intrinsic call that will
// get replaced
// with simple IR, so we care about `const op const` as well.
FgStack::FgSlot arg0;
bool isArg0Arg, isArg0Const, isArg1Const;
bool isArg1Arg, isArg0ConstArg, isArg1ConstArg;
if (ni == NI_SRCS_UNSAFE_IsNullRef)
{
// IsNullRef is unary, but it always compares against 0
arg0 = pushedStack.Top(0);
isArg0Arg = FgStack::IsArgument(arg0);
isArg0Const = FgStack::IsConstant(arg0);
isArg0ConstArg = FgStack::IsConstArgument(arg0, impInlineInfo);
isArg1Arg = false;
isArg1Const = true;
isArg1ConstArg = false;
}
else
{
arg0 = pushedStack.Top(1);
isArg0Arg = FgStack::IsArgument(arg0);
isArg0Const = FgStack::IsConstant(arg0);
isArg0ConstArg = FgStack::IsConstArgument(arg0, impInlineInfo);
FgStack::FgSlot arg1 = pushedStack.Top(0);
isArg1Arg = FgStack::IsArgument(arg0);
isArg1Const = FgStack::IsConstant(arg1);
isArg1ConstArg = FgStack::IsConstantOrConstArg(arg1, impInlineInfo);
}
// Const op ConstArg -> ConstArg
if (isArg0Const && isArg1ConstArg)
{
// keep stack unchanged
foldableIntrinsic = true;
}
// ConstArg op Const -> ConstArg
// ConstArg op ConstArg -> ConstArg
else if (isArg0ConstArg && (isArg1Const || isArg1ConstArg))
{
if (isArg1Const)
{
pushedStack.Push(arg0);
}
foldableIntrinsic = true;
}
// Const op Const -> Const
else if (isArg0Const && isArg1Const)
{
// both are constants so we still want to track this as foldable, unlike
// what is done for the regulary binary operator handling, since we have
// a CEE_CALL node and not something more primitive
foldableIntrinsic = true;
}
// Arg op ConstArg
// Arg op Const
else if (isArg0Arg && (isArg1Const || isArg1ConstArg))
{
// "Arg op CNS" --> keep arg0 in the stack for the next ops
pushedStack.Push(arg0);
handled = true;
// TODO-CQ: The normal binary operator handling pushes arg0
// and tracks this as CALLEE_BINARY_EXRP_WITH_CNS. We can't trivially
// do the same here without more work.
}
// ConstArg op Arg
// Const op Arg
else if (isArg1Arg && (isArg0Const || isArg0ConstArg))
{
// "CNS op ARG" --> keep arg1 in the stack for the next ops
handled = true;
// TODO-CQ: The normal binary operator handling keeps arg1
// and tracks this as CALLEE_BINARY_EXRP_WITH_CNS. We can't trivially
// do the same here without more work.
}
// X op ConstArg
if (isArg1ConstArg)
{
pushedStack.Push(arg0);
handled = true;
}
}
break;
}
case NI_SRCS_UNSAFE_AsPointer:
{
// These are effectively primitive unary operations so the
// handling roughly mirrors the handling for CEE_CONV_U and
// friends that exists elsewhere in this method
FgStack::FgSlot arg = pushedStack.Top();
if (FgStack::IsConstArgument(arg, impInlineInfo))
{
foldableIntrinsic = true;
}
else if (FgStack::IsArgument(arg))
{
handled = true;
}
else if (FgStack::IsConstant(arg))
{
// input is a constant so we still want to track this as foldable, unlike
// what is done for the regulary unary operator handling, since we have
// a CEE_CALL node and not something more primitive
foldableIntrinsic = true;
}
break;
}
#if defined(FEATURE_HW_INTRINSICS)
#if defined(TARGET_ARM64)
case NI_Vector64_As:
case NI_Vector64_AsByte:
case NI_Vector64_AsDouble:
case NI_Vector64_AsInt16:
case NI_Vector64_AsInt32:
case NI_Vector64_AsInt64:
case NI_Vector64_AsNInt:
case NI_Vector64_AsNUInt:
case NI_Vector64_AsSByte:
case NI_Vector64_AsSingle:
case NI_Vector64_AsUInt16:
case NI_Vector64_AsUInt32:
case NI_Vector64_AsUInt64:
case NI_Vector64_op_UnaryPlus:
#endif // TARGET_XARCH
case NI_Vector128_As:
case NI_Vector128_AsByte:
case NI_Vector128_AsDouble:
case NI_Vector128_AsInt16:
case NI_Vector128_AsInt32:
case NI_Vector128_AsInt64:
case NI_Vector128_AsNInt:
case NI_Vector128_AsNUInt:
case NI_Vector128_AsSByte:
case NI_Vector128_AsSingle:
case NI_Vector128_AsUInt16:
case NI_Vector128_AsUInt32:
case NI_Vector128_AsUInt64:
case NI_Vector128_AsVector4:
case NI_Vector128_op_UnaryPlus:
case NI_VectorT_As:
case NI_VectorT_AsVectorByte:
case NI_VectorT_AsVectorDouble:
case NI_VectorT_AsVectorInt16:
case NI_VectorT_AsVectorInt32:
case NI_VectorT_AsVectorInt64:
case NI_VectorT_AsVectorNInt:
case NI_VectorT_AsVectorNUInt:
case NI_VectorT_AsVectorSByte:
case NI_VectorT_AsVectorSingle:
case NI_VectorT_AsVectorUInt16:
case NI_VectorT_AsVectorUInt32:
case NI_VectorT_AsVectorUInt64:
case NI_VectorT_op_Explicit:
case NI_VectorT_op_UnaryPlus:
#if defined(TARGET_XARCH)
case NI_Vector256_As:
case NI_Vector256_AsByte:
case NI_Vector256_AsDouble:
case NI_Vector256_AsInt16:
case NI_Vector256_AsInt32:
case NI_Vector256_AsInt64:
case NI_Vector256_AsNInt:
case NI_Vector256_AsNUInt:
case NI_Vector256_AsSByte:
case NI_Vector256_AsSingle:
case NI_Vector256_AsUInt16:
case NI_Vector256_AsUInt32:
case NI_Vector256_AsUInt64:
case NI_Vector256_op_UnaryPlus:
case NI_Vector512_As:
case NI_Vector512_AsByte:
case NI_Vector512_AsDouble:
case NI_Vector512_AsInt16:
case NI_Vector512_AsInt32:
case NI_Vector512_AsInt64:
case NI_Vector512_AsNInt:
case NI_Vector512_AsNUInt:
case NI_Vector512_AsSByte:
case NI_Vector512_AsSingle:
case NI_Vector512_AsUInt16:
case NI_Vector512_AsUInt32:
case NI_Vector512_AsUInt64:
case NI_Vector512_op_UnaryPlus:
#endif // TARGET_XARCH
#endif // FEATURE_HW_INTRINSICS
case NI_SRCS_UNSAFE_As:
case NI_SRCS_UNSAFE_AsRef:
case NI_SRCS_UNSAFE_BitCast:
case NI_SRCS_UNSAFE_SkipInit:
{
// TODO-CQ: These are no-ops in that they never produce any IR
// and simply return op1 untouched. We should really track them
// as such and adjust the multiplier even more, but we'll settle
// for marking it as foldable until additional work can happen.
foldableIntrinsic = true;
break;
}
#if defined(FEATURE_HW_INTRINSICS)
#if defined(TARGET_ARM64)
case NI_Vector64_get_AllBitsSet:
case NI_Vector64_get_One:
case NI_Vector64_get_Zero:
#endif // TARGET_ARM64
case NI_Vector128_get_AllBitsSet:
case NI_Vector128_get_One:
case NI_Vector128_get_Zero:
case NI_VectorT_get_AllBitsSet:
case NI_VectorT_get_One:
case NI_VectorT_get_Zero:
#if defined(TARGET_XARCH)
case NI_Vector256_get_AllBitsSet:
case NI_Vector256_get_One:
case NI_Vector256_get_Zero:
case NI_Vector512_get_AllBitsSet:
case NI_Vector512_get_One:
case NI_Vector512_get_Zero:
#endif // TARGET_XARCH
#endif // FEATURE_HW_INTRINSICS
{
// These always produce a vector constant
foldableIntrinsic = true;
// TODO-CQ: We should really push a constant onto the stack
// However, this isn't trivially possible without the inliner
// understanding a new type of "vector constant" so it doesn't
// negatively impact other possible checks/handling
break;
}
case NI_SRCS_UNSAFE_NullRef:
case NI_SRCS_UNSAFE_SizeOf:
{
// These always produce a constant
foldableIntrinsic = true;
pushedStack.PushConstant();
break;
}
default:
{
break;
}
}
}
if (foldableIntrinsic)
{
compInlineResult->Note(InlineObservation::CALLSITE_FOLDABLE_INTRINSIC);
handled = true;
}
else if (ni != NI_Illegal)
{
// Otherwise note "intrinsic" (most likely will be lowered as single instructions)
// except Math where only a few intrinsics won't end up as normal calls
if (!IsMathIntrinsic(ni) || IsTargetIntrinsic(ni))
{
compInlineResult->Note(InlineObservation::CALLEE_INTRINSIC);
}
}
}
if ((codeAddr < codeEndp - sz) && (OPCODE)getU1LittleEndian(codeAddr + sz) == CEE_RET)
{
// If the method has a call followed by a ret, assume that
// it is a wrapper method.
compInlineResult->Note(InlineObservation::CALLEE_LOOKS_LIKE_WRAPPER);
}
if (!isIntrinsic && !handled && FgStack::IsArgument(pushedStack.Top()))
{
// Optimistically assume that "call(arg)" returns something arg-dependent.
// However, we don't know how many args it expects and its return type.
handled = true;
}
}
break;
case CEE_LDIND_I1:
case CEE_LDIND_U1:
case CEE_LDIND_I2:
case CEE_LDIND_U2:
case CEE_LDIND_I4:
case CEE_LDIND_U4:
case CEE_LDIND_I8:
case CEE_LDIND_I:
case CEE_LDIND_R4:
case CEE_LDIND_R8:
case CEE_LDIND_REF:
{
if (FgStack::IsArgument(pushedStack.Top()))
{
handled = true;
}
break;
}
// Unary operators:
case CEE_CONV_I:
case CEE_CONV_U:
case CEE_CONV_I1:
case CEE_CONV_I2:
case CEE_CONV_I4:
case CEE_CONV_I8:
case CEE_CONV_R4:
case CEE_CONV_R8:
case CEE_CONV_U4:
case CEE_CONV_U8:
case CEE_CONV_U2:
case CEE_CONV_U1:
case CEE_CONV_R_UN:
case CEE_CONV_OVF_I:
case CEE_CONV_OVF_U:
case CEE_CONV_OVF_I1:
case CEE_CONV_OVF_U1:
case CEE_CONV_OVF_I2:
case CEE_CONV_OVF_U2:
case CEE_CONV_OVF_I4:
case CEE_CONV_OVF_U4:
case CEE_CONV_OVF_I8:
case CEE_CONV_OVF_U8:
case CEE_CONV_OVF_I_UN:
case CEE_CONV_OVF_U_UN:
case CEE_CONV_OVF_I1_UN:
case CEE_CONV_OVF_I2_UN:
case CEE_CONV_OVF_I4_UN:
case CEE_CONV_OVF_I8_UN:
case CEE_CONV_OVF_U1_UN:
case CEE_CONV_OVF_U2_UN:
case CEE_CONV_OVF_U4_UN:
case CEE_CONV_OVF_U8_UN:
case CEE_NOT:
case CEE_NEG:
{
if (makeInlineObservations)
{
FgStack::FgSlot arg = pushedStack.Top();
if (FgStack::IsConstArgument(arg, impInlineInfo))
{
compInlineResult->Note(InlineObservation::CALLSITE_FOLDABLE_EXPR_UN);
handled = true;
}
else if (FgStack::IsArgument(arg) || FgStack::IsConstant(arg))
{
handled = true;
}
}
break;
}
// Binary operators:
case CEE_ADD:
case CEE_SUB:
case CEE_MUL:
case CEE_DIV:
case CEE_DIV_UN:
case CEE_REM:
case CEE_REM_UN:
case CEE_AND:
case CEE_OR:
case CEE_XOR:
case CEE_SHL:
case CEE_SHR:
case CEE_SHR_UN:
case CEE_ADD_OVF:
case CEE_ADD_OVF_UN:
case CEE_MUL_OVF:
case CEE_MUL_OVF_UN:
case CEE_SUB_OVF:
case CEE_SUB_OVF_UN:
case CEE_CEQ:
case CEE_CGT:
case CEE_CGT_UN:
case CEE_CLT:
case CEE_CLT_UN:
{
if (!makeInlineObservations)
{
break;
}
if (!preciseScan)
{
switch (opcode)
{
case CEE_CEQ:
case CEE_CGT:
case CEE_CGT_UN:
case CEE_CLT:
case CEE_CLT_UN:
fgObserveInlineConstants(opcode, pushedStack, isInlining);
break;
default:
break;
}
}
else
{
FgStack::FgSlot arg0 = pushedStack.Top(1);
FgStack::FgSlot arg1 = pushedStack.Top(0);
// Const op ConstArg -> ConstArg
if (FgStack::IsConstant(arg0) && FgStack::IsConstArgument(arg1, impInlineInfo))
{
// keep stack unchanged
handled = true;
compInlineResult->Note(InlineObservation::CALLSITE_FOLDABLE_EXPR);
}
// ConstArg op Const -> ConstArg
// ConstArg op ConstArg -> ConstArg
else if (FgStack::IsConstArgument(arg0, impInlineInfo) &&
FgStack::IsConstantOrConstArg(arg1, impInlineInfo))
{
if (FgStack::IsConstant(arg1))
{
pushedStack.Push(arg0);
}
handled = true;
compInlineResult->Note(InlineObservation::CALLSITE_FOLDABLE_EXPR);
}
// Const op Const -> Const
else if (FgStack::IsConstant(arg0) && FgStack::IsConstant(arg1))
{
// both are constants, but we're mostly interested in cases where a const arg leads to
// a foldable expression.
handled = true;
}
// Arg op ConstArg
// Arg op Const
else if (FgStack::IsArgument(arg0) && FgStack::IsConstantOrConstArg(arg1, impInlineInfo))
{
// "Arg op CNS" --> keep arg0 in the stack for the next ops
pushedStack.Push(arg0);
handled = true;
compInlineResult->Note(InlineObservation::CALLEE_BINARY_EXRP_WITH_CNS);
}
// ConstArg op Arg
// Const op Arg
else if (FgStack::IsArgument(arg1) && FgStack::IsConstantOrConstArg(arg0, impInlineInfo))
{
// "CNS op ARG" --> keep arg1 in the stack for the next ops
handled = true;
compInlineResult->Note(InlineObservation::CALLEE_BINARY_EXRP_WITH_CNS);
}
// X / ConstArg
// X % ConstArg
if (FgStack::IsConstArgument(arg1, impInlineInfo))
{
if ((opcode == CEE_DIV) || (opcode == CEE_DIV_UN) || (opcode == CEE_REM) ||
(opcode == CEE_REM_UN))
{
compInlineResult->Note(InlineObservation::CALLSITE_DIV_BY_CNS);
}
pushedStack.Push(arg0);
handled = true;
}
}
break;
}
// Jumps
case CEE_LEAVE:
case CEE_LEAVE_S:
case CEE_BR:
case CEE_BR_S:
case CEE_BRFALSE:
case CEE_BRFALSE_S:
case CEE_BRTRUE:
case CEE_BRTRUE_S:
case CEE_BEQ:
case CEE_BEQ_S:
case CEE_BGE:
case CEE_BGE_S:
case CEE_BGE_UN:
case CEE_BGE_UN_S:
case CEE_BGT:
case CEE_BGT_S:
case CEE_BGT_UN:
case CEE_BGT_UN_S:
case CEE_BLE:
case CEE_BLE_S:
case CEE_BLE_UN:
case CEE_BLE_UN_S:
case CEE_BLT:
case CEE_BLT_S:
case CEE_BLT_UN:
case CEE_BLT_UN_S:
case CEE_BNE_UN:
case CEE_BNE_UN_S:
{
if (codeAddr > codeEndp - sz)
{
goto TOO_FAR;
}
// Compute jump target address
signed jmpDist = (sz == 1) ? getI1LittleEndian(codeAddr) : getI4LittleEndian(codeAddr);
if ((jmpDist == 0) &&
(opcode == CEE_LEAVE || opcode == CEE_LEAVE_S || opcode == CEE_BR || opcode == CEE_BR_S) &&
opts.DoEarlyBlockMerging())
{
break; /* NOP */
}
unsigned jmpAddr = (IL_OFFSET)(codeAddr - codeBegp) + sz + jmpDist;
// Make sure target is reasonable
if (jmpAddr >= codeSize)
{
BADCODE3("code jumps to outer space", " at offset %04X", (IL_OFFSET)(codeAddr - codeBegp));
}
if (makeInlineObservations && (jmpDist < 0))
{
compInlineResult->Note(InlineObservation::CALLEE_BACKWARD_JUMP);
}
// Mark the jump target
jumpTarget->bitVectSet(jmpAddr);
// See if jump might be sensitive to inlining
if (!preciseScan && makeInlineObservations && (opcode != CEE_BR_S) && (opcode != CEE_BR))
{
fgObserveInlineConstants(opcode, pushedStack, isInlining);
}
else if (preciseScan && makeInlineObservations)
{
switch (opcode)
{
// Binary
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_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:
{
FgStack::FgSlot op1 = pushedStack.Top(1);
FgStack::FgSlot op2 = pushedStack.Top(0);
if (FgStack::IsConstantOrConstArg(op1, impInlineInfo) &&
FgStack::IsConstantOrConstArg(op2, impInlineInfo))
{
compInlineResult->Note(InlineObservation::CALLSITE_FOLDABLE_BRANCH);
}
if (FgStack::IsConstArgument(op1, impInlineInfo) ||
FgStack::IsConstArgument(op2, impInlineInfo))
{
compInlineResult->Note(InlineObservation::CALLSITE_CONSTANT_ARG_FEEDS_TEST);
}
if ((FgStack::IsArgument(op1) && FgStack::IsArrayLen(op2)) ||
(FgStack::IsArgument(op2) && FgStack::IsArrayLen(op1)))
{
compInlineResult->Note(InlineObservation::CALLEE_ARG_FEEDS_RANGE_CHECK);
}
else if ((FgStack::IsArgument(op1) && FgStack::IsConstantOrConstArg(op2, impInlineInfo)) ||
(FgStack::IsArgument(op2) && FgStack::IsConstantOrConstArg(op1, impInlineInfo)))
{
compInlineResult->Note(InlineObservation::CALLEE_ARG_FEEDS_CONSTANT_TEST);
}
else if (FgStack::IsArgument(op1) || FgStack::IsArgument(op2))
{
compInlineResult->Note(InlineObservation::CALLEE_ARG_FEEDS_TEST);
}
else if (FgStack::IsConstant(op1) || FgStack::IsConstant(op2))
{
compInlineResult->Note(InlineObservation::CALLEE_BINARY_EXRP_WITH_CNS);
}
break;
}
// Unary
case CEE_BRFALSE_S:
case CEE_BRTRUE_S:
case CEE_BRFALSE:
case CEE_BRTRUE:
{
if (FgStack::IsConstantOrConstArg(pushedStack.Top(), impInlineInfo))
{
compInlineResult->Note(InlineObservation::CALLSITE_FOLDABLE_BRANCH);
}
else if (FgStack::IsArgument(pushedStack.Top()))
{
// E.g. brtrue is basically "if (X == 0)"
compInlineResult->Note(InlineObservation::CALLEE_ARG_FEEDS_CONSTANT_TEST);
}
break;
}
default:
break;
}
}
}
break;
case CEE_LDFLDA:
case CEE_LDFLD:
case CEE_STFLD:
{
if (FgStack::IsArgument(pushedStack.Top()))
{
compInlineResult->Note(InlineObservation::CALLEE_ARG_STRUCT_FIELD_ACCESS);
handled = true; // keep argument on top of the stack
}
break;
}
case CEE_LDELEM_I1:
case CEE_LDELEM_U1:
case CEE_LDELEM_I2:
case CEE_LDELEM_U2:
case CEE_LDELEM_I4:
case CEE_LDELEM_U4:
case CEE_LDELEM_I8:
case CEE_LDELEM_I:
case CEE_LDELEM_R4:
case CEE_LDELEM_R8:
case CEE_LDELEM_REF:
case CEE_STELEM_I:
case CEE_STELEM_I1:
case CEE_STELEM_I2:
case CEE_STELEM_I4:
case CEE_STELEM_I8:
case CEE_STELEM_R4:
case CEE_STELEM_R8:
case CEE_STELEM_REF:
case CEE_LDELEM:
case CEE_STELEM:
{
if (!preciseScan)
{
break;
}
if (FgStack::IsArgument(pushedStack.Top()) || FgStack::IsArgument(pushedStack.Top(1)))
{
compInlineResult->Note(InlineObservation::CALLEE_ARG_FEEDS_RANGE_CHECK);
}
break;
}
case CEE_SWITCH:
{
if (makeInlineObservations)
{
compInlineResult->Note(InlineObservation::CALLEE_HAS_SWITCH);
if (FgStack::IsConstantOrConstArg(pushedStack.Top(), impInlineInfo))
{
compInlineResult->Note(InlineObservation::CALLSITE_FOLDABLE_SWITCH);
}
// Fail fast, if we're inlining and can't handle this.
if (isInlining && compInlineResult->IsFailure())
{
return;
}
}
// Make sure we don't go past the end reading the number of cases
if (codeAddr > codeEndp - sizeof(DWORD))
{
goto TOO_FAR;
}
// Read the number of cases
unsigned jmpCnt = getU4LittleEndian(codeAddr);
codeAddr += sizeof(DWORD);
if (jmpCnt > codeSize / sizeof(DWORD))
{
goto TOO_FAR;
}
// Find the end of the switch table
unsigned jmpBase = (unsigned)((codeAddr - codeBegp) + jmpCnt * sizeof(DWORD));
// Make sure there is more code after the switch
if (jmpBase >= codeSize)
{
goto TOO_FAR;
}
// jmpBase is also the target of the default case, so mark it
jumpTarget->bitVectSet(jmpBase);
// Process table entries
while (jmpCnt > 0)
{
unsigned jmpAddr = jmpBase + getI4LittleEndian(codeAddr);
codeAddr += 4;
if (jmpAddr >= codeSize)
{
BADCODE3("jump target out of range", " at offset %04X", (IL_OFFSET)(codeAddr - codeBegp));
}
jumpTarget->bitVectSet(jmpAddr);
jmpCnt--;
}
// We've advanced past all the bytes in this instruction
sz = 0;
}
break;
case CEE_UNALIGNED:
{
noway_assert(sz == sizeof(int8_t));
prefixFlags |= PREFIX_UNALIGNED;
codeAddr += sizeof(int8_t);
impValidateMemoryAccessOpcode(codeAddr, codeEndp, false);
handled = true;
goto OBSERVE_OPCODE;
}
case CEE_CONSTRAINED:
{
noway_assert(sz == sizeof(unsigned));
prefixFlags |= PREFIX_CONSTRAINED;
codeAddr += sizeof(unsigned);
{
OPCODE actualOpcode = impGetNonPrefixOpcode(codeAddr, codeEndp);
if (actualOpcode != CEE_CALLVIRT && actualOpcode != CEE_CALL && actualOpcode != CEE_LDFTN)
{
BADCODE("constrained. has to be followed by callvirt, call or ldftn");
}
}
handled = true;
goto OBSERVE_OPCODE;
}
case CEE_READONLY:
{
noway_assert(sz == 0);
prefixFlags |= PREFIX_READONLY;
{
OPCODE actualOpcode = impGetNonPrefixOpcode(codeAddr, codeEndp);
if ((actualOpcode != CEE_LDELEMA) && !impOpcodeIsCallOpcode(actualOpcode))
{
BADCODE("readonly. has to be followed by ldelema or call");
}
}
handled = true;
goto OBSERVE_OPCODE;
}
case CEE_VOLATILE:
{
noway_assert(sz == 0);
prefixFlags |= PREFIX_VOLATILE;
impValidateMemoryAccessOpcode(codeAddr, codeEndp, true);
handled = true;
goto OBSERVE_OPCODE;
}
case CEE_TAILCALL:
{
noway_assert(sz == 0);
prefixFlags |= PREFIX_TAILCALL_EXPLICIT;
{
OPCODE actualOpcode = impGetNonPrefixOpcode(codeAddr, codeEndp);
if (!impOpcodeIsCallOpcode(actualOpcode))
{
BADCODE("tailcall. has to be followed by call, callvirt or calli");
}
}
handled = true;
goto OBSERVE_OPCODE;
}
case CEE_STARG:
case CEE_STARG_S:
{
noway_assert(sz == sizeof(BYTE) || sz == sizeof(WORD));
if (codeAddr > codeEndp - sz)
{
goto TOO_FAR;
}
varNum = (sz == sizeof(BYTE)) ? getU1LittleEndian(codeAddr) : getU2LittleEndian(codeAddr);
if (isInlining)
{
if (varNum < impInlineInfo->argCnt)
{
impInlineInfo->inlArgInfo[varNum].argHasStargOp = true;
}
}
else
{
// account for possible hidden param
varNum = compMapILargNum(varNum);
// This check is only intended to prevent an AV. Bad varNum values will later
// be handled properly by the verifier.
if (varNum < lvaTableCnt)
{
// In non-inline cases, note written-to arguments.
lvaTable[varNum].lvHasILStoreOp = 1;
}
}
}
break;
case CEE_STLOC_0:
case CEE_STLOC_1:
case CEE_STLOC_2:
case CEE_STLOC_3:
varNum = (opcode - CEE_STLOC_0);
goto STLOC;
case CEE_STLOC:
case CEE_STLOC_S:
{
noway_assert(sz == sizeof(BYTE) || sz == sizeof(WORD));
if (codeAddr > codeEndp - sz)
{
goto TOO_FAR;
}
varNum = (sz == sizeof(BYTE)) ? getU1LittleEndian(codeAddr) : getU2LittleEndian(codeAddr);
STLOC:
if (isInlining)
{
InlLclVarInfo& lclInfo = impInlineInfo->lclVarInfo[varNum + impInlineInfo->argCnt];
if (lclInfo.lclHasStlocOp)
{
lclInfo.lclHasMultipleStlocOp = 1;
}
else
{
lclInfo.lclHasStlocOp = 1;
}
}
else
{
varNum += info.compArgsCount;
// This check is only intended to prevent an AV. Bad varNum values will later
// be handled properly by the verifier.
if (varNum < lvaTableCnt)
{
// In non-inline cases, note written-to locals.
if (lvaTable[varNum].lvHasILStoreOp)
{
lvaTable[varNum].lvHasMultipleILStoreOp = 1;
}
else
{
lvaTable[varNum].lvHasILStoreOp = 1;
}
}
}
}
break;
case CEE_LDLOC_0:
case CEE_LDLOC_1:
case CEE_LDLOC_2:
case CEE_LDLOC_3:
//
if (preciseScan && makeInlineObservations && (prevOpcode == (CEE_STLOC_3 - (CEE_LDLOC_3 - opcode))))
{
// Fold stloc+ldloc
pushedStack.Push(pushedStack.Top(1)); // throw away SLOT_UNKNOWN inserted by STLOC
handled = true;
}
break;
case CEE_LDARGA:
case CEE_LDARGA_S:
case CEE_LDLOCA:
case CEE_LDLOCA_S:
{
// Handle address-taken args or locals
noway_assert(sz == sizeof(BYTE) || sz == sizeof(WORD));
if (codeAddr > codeEndp - sz)
{
goto TOO_FAR;
}
varNum = (sz == sizeof(BYTE)) ? getU1LittleEndian(codeAddr) : getU2LittleEndian(codeAddr);
if (isInlining)
{
if (opcode == CEE_LDLOCA || opcode == CEE_LDLOCA_S)
{
varType = impInlineInfo->lclVarInfo[varNum + impInlineInfo->argCnt].lclTypeInfo;
impInlineInfo->lclVarInfo[varNum + impInlineInfo->argCnt].lclHasLdlocaOp = true;
}
else
{
noway_assert(opcode == CEE_LDARGA || opcode == CEE_LDARGA_S);
varType = impInlineInfo->lclVarInfo[varNum].lclTypeInfo;
impInlineInfo->inlArgInfo[varNum].argHasLdargaOp = true;
pushedStack.PushArgument(varNum);
handled = true;
}
}
else
{
if (opcode == CEE_LDLOCA || opcode == CEE_LDLOCA_S)
{
if (varNum >= info.compMethodInfo->locals.numArgs)
{
BADCODE("bad local number");
}
varNum += info.compArgsCount;
}
else
{
noway_assert(opcode == CEE_LDARGA || opcode == CEE_LDARGA_S);
if (varNum >= info.compILargsCount)
{
BADCODE("bad argument number");
}
varNum = compMapILargNum(varNum); // account for possible hidden param
}
varType = (var_types)lvaTable[varNum].lvType;
// Determine if the next instruction will consume
// the address. If so we won't mark this var as
// address taken.
//
// We will put structs on the stack and changing
// the addrTaken of a local requires an extra pass
// in the morpher so we won't apply this
// optimization to structs.
//
// Debug code spills for every IL instruction, and
// therefore it will split statements, so we will
// need the address. Note that this optimization
// is based in that we know what trees we will
// generate for this ldfld, and we require that we
// won't need the address of this local at all
const bool notStruct = !varTypeIsStruct(lvaGetDesc(varNum));
const bool notLastInstr = (codeAddr < codeEndp - sz);
const bool notDebugCode = !opts.compDbgCode;
if (notStruct && notLastInstr && notDebugCode && impILConsumesAddr(codeAddr + sz))
{
// We can skip the addrtaken, as next IL instruction consumes
// the address.
}
else
{
lvaTable[varNum].lvHasLdAddrOp = 1;
if (!info.compIsStatic && (varNum == 0))
{
// Addr taken on "this" pointer is significant,
// go ahead to mark it as permanently addr-exposed here.
// This may be conservative, but probably not very.
lvaSetVarAddrExposed(0 DEBUGARG(AddressExposedReason::TOO_CONSERVATIVE));
}
}
} // isInlining
typeIsNormed = !varTypeIsGC(varType) && !varTypeIsStruct(varType);
}
break;
case CEE_JMP:
retBlocks++;
#if !defined(TARGET_X86) && !defined(TARGET_ARM)
if (!isInlining)
{
// We transform this into a set of ldarg's + tail call and
// thus may push more onto the stack than originally thought.
// This doesn't interfere with verification because CEE_JMP
// is never verifiable, and there's nothing unsafe you can
// do with a an IL stack overflow if the JIT is expecting it.
info.compMaxStack = max(info.compMaxStack, info.compILargsCount);
break;
}
#endif // !TARGET_X86 && !TARGET_ARM
// If we are inlining, we need to fail for a CEE_JMP opcode, just like
// the list of other opcodes (for all platforms).
FALLTHROUGH;
case CEE_MKREFANY:
case CEE_RETHROW:
if (makeInlineObservations)
{
// Arguably this should be NoteFatal, but the legacy behavior is
// to ignore this for the prejit root.
compInlineResult->Note(InlineObservation::CALLEE_UNSUPPORTED_OPCODE);
// Fail fast if we're inlining...
if (isInlining)
{
assert(compInlineResult->IsFailure());
return;
}
}
break;
case CEE_LOCALLOC:
compLocallocSeen = true;
// We now allow localloc callees to become candidates in some cases.
if (makeInlineObservations)
{
compInlineResult->Note(InlineObservation::CALLEE_HAS_LOCALLOC);
if (isInlining && compInlineResult->IsFailure())
{
return;
}
}
break;
case CEE_LDARG_0:
case CEE_LDARG_1:
case CEE_LDARG_2:
case CEE_LDARG_3:
if (makeInlineObservations)
{
pushedStack.PushArgument(opcode - CEE_LDARG_0);
handled = true;
}
break;
case CEE_LDARG_S:
case CEE_LDARG:
{
if (codeAddr > codeEndp - sz)
{
goto TOO_FAR;
}
varNum = (sz == sizeof(BYTE)) ? getU1LittleEndian(codeAddr) : getU2LittleEndian(codeAddr);
if (makeInlineObservations)
{
pushedStack.PushArgument(varNum);
handled = true;
}
}
break;
case CEE_LDLEN:
if (makeInlineObservations)
{
pushedStack.PushArrayLen();
handled = true;
}
break;
case CEE_RET:
retBlocks++;
break;
default:
break;
}
// Skip any remaining operands this opcode may have
codeAddr += sz;
// Clear any prefix flags that may have been set
prefixFlags = 0;
// Increment the number of observed instructions
opts.instrCount++;
OBSERVE_OPCODE:
// Note the opcode we just saw
if (makeInlineObservations)
{
InlineObservation obs =
typeIsNormed ? InlineObservation::CALLEE_OPCODE_NORMED : InlineObservation::CALLEE_OPCODE;
compInlineResult->NoteInt(obs, opcode);
}
typeIsNormed = false;
}
if (codeAddr != codeEndp)
{
TOO_FAR:
BADCODE3("Code ends in the middle of an opcode, or there is a branch past the end of the method",
" at offset %04X", (IL_OFFSET)(codeAddr - codeBegp));
}
INDEBUG(compInlineContext->SetILInstsSet(ilInstsSet));
if (makeInlineObservations)
{
compInlineResult->Note(InlineObservation::CALLEE_END_OPCODE_SCAN);
// If there are no return blocks we know it does not return, however if there
// return blocks we don't know it returns as it may be counting unreachable code.
// However we will still make the CALLEE_DOES_NOT_RETURN observation.
compInlineResult->NoteBool(InlineObservation::CALLEE_DOES_NOT_RETURN, retBlocks == 0);
if ((retBlocks == 0) && isInlining &&
info.compCompHnd->notifyMethodInfoUsage(impInlineInfo->iciCall->gtCallMethHnd))
{
// Mark the call node as "no return" as it can impact caller's code quality.
setCallDoesNotReturn(impInlineInfo->iciCall);
// NOTE: we also ask VM whether we're allowed to do so - we don't want to mark a call
// as "no-return" if its IL may change.
}
// If the inline is viable and discretionary, do the
// profitability screening.
if (compInlineResult->IsDiscretionaryCandidate())
{
// Make some callsite specific observations that will feed
// into the profitability model.
impMakeDiscretionaryInlineObservations(impInlineInfo, compInlineResult);
// None of those observations should have changed the
// inline's viability.
assert(compInlineResult->IsCandidate());
if (isInlining)
{
// Assess profitability...
CORINFO_METHOD_INFO* methodInfo = &impInlineInfo->inlineCandidateInfo->methInfo;
compInlineResult->DetermineProfitability(methodInfo);
if (compInlineResult->IsFailure())
{
impInlineRoot()->m_inlineStrategy->NoteUnprofitable();
JITDUMP("\n\nInline expansion aborted, inline not profitable\n");
return;
}
else
{
// The inline is still viable.
assert(compInlineResult->IsCandidate());
}
}
else
{
// Prejit root case. Profitability assessment for this
// is done over in compCompileHelper.
}
}
}
// None of the local vars in the inlinee should have address taken or been written to.
// Therefore we should NOT need to enter this "if" statement.
if (!isInlining && !info.compIsStatic)
{
fgAdjustForAddressExposedOrWrittenThis();
}
// Now that we've seen the IL, set lvSingleDef for root method
// locals.
//
// We could also do this for root method arguments but single-def
// arguments are set by the caller and so we don't know anything
// about the possible values or types.
//
// For inlinees we do this over in impInlineFetchLocal and
// impInlineFetchArg (here args are included as we sometimes get
// new information about the types of inlinee args).
if (!isInlining)
{
const unsigned firstLcl = info.compArgsCount;
const unsigned lastLcl = firstLcl + info.compMethodInfo->locals.numArgs;
for (unsigned lclNum = firstLcl; lclNum < lastLcl; lclNum++)
{
LclVarDsc* lclDsc = lvaGetDesc(lclNum);
assert(lclDsc->lvSingleDef == 0);
lclDsc->lvSingleDef = !lclDsc->lvHasMultipleILStoreOp && !lclDsc->lvHasLdAddrOp;
if (lclDsc->lvSingleDef)
{
JITDUMP("Marked V%02u as a single def local\n", lclNum);
}
}
}
}