summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Ayers <andya@microsoft.com>2017-10-09 19:27:28 -0700
committerGitHub <noreply@github.com>2017-10-09 19:27:28 -0700
commitbe3715cbba1022c95bb668d45521016b0414bc25 (patch)
tree8b966973bd31407c131d8b769dfa6330987875c8
parent8ec9b34df095df032e37b4c20a7359e9df7ae0ce (diff)
downloadcoreclr-be3715cbba1022c95bb668d45521016b0414bc25.tar.gz
coreclr-be3715cbba1022c95bb668d45521016b0414bc25.tar.bz2
coreclr-be3715cbba1022c95bb668d45521016b0414bc25.zip
JIT: improve type equality opts for generic and prejitted code (#14381)
Handle cases where a `GetType()` call on a generically typed object feeds into a type equality comparison. These calls have constraint prefixes. For value classes the constraint tells us the type and so we can avoid the box and call and just construct that type directly. For ref classes the type test can usually reduce to a method table comparison. Also, handle cases that arise in prejiited code better, by generalizing how the jit looks for class handles from type construction trees. Added test cases. Closes #14304.
-rw-r--r--src/jit/compiler.h22
-rw-r--r--src/jit/gentree.cpp171
-rw-r--r--src/jit/importer.cpp196
-rw-r--r--tests/src/JIT/opt/Types/Equality.cs90
-rw-r--r--tests/src/JIT/opt/Types/Equality.csproj39
5 files changed, 397 insertions, 121 deletions
diff --git a/src/jit/compiler.h b/src/jit/compiler.h
index 0e8b4c2135..079878a229 100644
--- a/src/jit/compiler.h
+++ b/src/jit/compiler.h
@@ -3001,16 +3001,18 @@ protected:
void impImportLeave(BasicBlock* block);
void impResetLeaveBlock(BasicBlock* block, unsigned jmpAddr);
- GenTreePtr impIntrinsic(GenTreePtr newobjThis,
- CORINFO_CLASS_HANDLE clsHnd,
- CORINFO_METHOD_HANDLE method,
- CORINFO_SIG_INFO* sig,
- int memberRef,
- bool readonlyCall,
- bool tailCall,
- bool isJitIntrinsic,
- CorInfoIntrinsics* pIntrinsicID,
- bool* isSpecialIntrinsic = nullptr);
+ GenTree* impIntrinsic(GenTree* newobjThis,
+ CORINFO_CLASS_HANDLE clsHnd,
+ CORINFO_METHOD_HANDLE method,
+ CORINFO_SIG_INFO* sig,
+ int memberRef,
+ bool readonlyCall,
+ bool tailCall,
+ CORINFO_RESOLVED_TOKEN* pContstrainedResolvedToken,
+ CORINFO_THIS_TRANSFORM constraintCallThisTransform,
+ bool isJitIntrinsic,
+ CorInfoIntrinsics* pIntrinsicID,
+ bool* isSpecialIntrinsic = nullptr);
GenTree* impMathIntrinsic(CORINFO_METHOD_HANDLE method,
CORINFO_SIG_INFO* sig,
var_types callType,
diff --git a/src/jit/gentree.cpp b/src/jit/gentree.cpp
index b2cfc4458e..01dc3d54e1 100644
--- a/src/jit/gentree.cpp
+++ b/src/jit/gentree.cpp
@@ -12399,55 +12399,52 @@ GenTree* Compiler::gtFoldTypeCompare(GenTree* tree)
if (op1IsFromHandle && op2IsFromHandle)
{
JITDUMP("Optimizing compare of types-from-handles to instead compare handles\n");
- GenTree* op1ClassFromHandle = tree->gtOp.gtOp1->gtCall.gtCallArgs->gtOp.gtOp1;
- GenTree* op2ClassFromHandle = tree->gtOp.gtOp2->gtCall.gtCallArgs->gtOp.gtOp1;
+ GenTree* op1ClassFromHandle = tree->gtOp.gtOp1->gtCall.gtCallArgs->gtOp.gtOp1;
+ GenTree* op2ClassFromHandle = tree->gtOp.gtOp2->gtCall.gtCallArgs->gtOp.gtOp1;
+ GenTree* op1TunneledHandle = nullptr;
+ GenTree* op2TunneledHandle = nullptr;
+ CORINFO_CLASS_HANDLE cls1Hnd = nullptr;
+ CORINFO_CLASS_HANDLE cls2Hnd = nullptr;
+ unsigned runtimeLookupCount = 0;
- // If we see indirs, tunnel through to see if there are compile time handles.
- if ((op1ClassFromHandle->gtOper == GT_IND) && (op2ClassFromHandle->gtOper == GT_IND))
+ // Try and find class handle for op1
+ if ((op1ClassFromHandle->gtOper == GT_CNS_INT) && (op1ClassFromHandle->gtType == TYP_I_IMPL))
+ {
+ assert(op1ClassFromHandle->IsIconHandle(GTF_ICON_CLASS_HDL));
+ cls1Hnd = (CORINFO_CLASS_HANDLE)op1ClassFromHandle->gtIntCon.gtCompileTimeHandle;
+ }
+ else if (op1ClassFromHandle->OperGet() == GT_RUNTIMELOOKUP)
+ {
+ cls1Hnd = op1ClassFromHandle->AsRuntimeLookup()->GetClassHandle();
+ runtimeLookupCount++;
+ }
+ // Tunnel through indirs we may see when prejitting
+ else if (op1ClassFromHandle->gtOper == GT_IND)
{
// The handle indirs we can optimize will be marked as non-faulting.
// Certain others (eg from refanytype) may not be.
- if (((op1ClassFromHandle->gtFlags & GTF_IND_NONFAULTING) != 0) &&
- ((op2ClassFromHandle->gtFlags & GTF_IND_NONFAULTING) != 0))
+ if (op1ClassFromHandle->gtFlags & GTF_IND_NONFAULTING)
{
GenTree* op1HandleLiteral = op1ClassFromHandle->gtOp.gtOp1;
- GenTree* op2HandleLiteral = op2ClassFromHandle->gtOp.gtOp1;
- // If, after tunneling, we have constant handles on both
- // sides, update the operands that will feed the compare.
- if ((op1HandleLiteral->gtOper == GT_CNS_INT) && (op1HandleLiteral->gtType == TYP_I_IMPL) &&
- (op2HandleLiteral->gtOper == GT_CNS_INT) && (op2HandleLiteral->gtType == TYP_I_IMPL))
+ // If, after tunneling, we have a constant handle,
+ // remember the class and the value tree for later.
+ if ((op1HandleLiteral->gtOper == GT_CNS_INT) && (op1HandleLiteral->gtType == TYP_I_IMPL))
{
- JITDUMP("...tunneling through indirs...\n");
- op1ClassFromHandle = op1HandleLiteral;
- op2ClassFromHandle = op2HandleLiteral;
+ JITDUMP("tunneling through indir on op1\n");
+ op1TunneledHandle = op1HandleLiteral;
// These handle constants should be class handles.
- assert(op1ClassFromHandle->IsIconHandle(GTF_ICON_CLASS_HDL));
- assert(op2ClassFromHandle->IsIconHandle(GTF_ICON_CLASS_HDL));
+ assert(op1TunneledHandle->IsIconHandle(GTF_ICON_CLASS_HDL));
+ cls1Hnd = (CORINFO_CLASS_HANDLE)op1TunneledHandle->gtIntCon.gtCompileTimeHandle;
}
}
}
- // If the inputs to the type from handle operations are now
- // either known class handles or runtime lookups, ask the VM
- // if it knows the outcome of the equality comparison.
- CORINFO_CLASS_HANDLE cls1Hnd = nullptr;
- CORINFO_CLASS_HANDLE cls2Hnd = nullptr;
- unsigned runtimeLookupCount = 0;
-
- if ((op1ClassFromHandle->OperGet() == GT_CNS_INT) && op1ClassFromHandle->IsIconHandle(GTF_ICON_CLASS_HDL))
- {
- cls1Hnd = (CORINFO_CLASS_HANDLE)op1ClassFromHandle->gtIntCon.gtCompileTimeHandle;
- }
- else if (op1ClassFromHandle->OperGet() == GT_RUNTIMELOOKUP)
- {
- cls1Hnd = op1ClassFromHandle->AsRuntimeLookup()->GetClassHandle();
- runtimeLookupCount++;
- }
-
- if ((op2ClassFromHandle->OperGet() == GT_CNS_INT) && op2ClassFromHandle->IsIconHandle(GTF_ICON_CLASS_HDL))
+ // Try and find class handle for op2
+ if ((op2ClassFromHandle->gtOper == GT_CNS_INT) && (op2ClassFromHandle->gtType == TYP_I_IMPL))
{
+ assert(op2ClassFromHandle->IsIconHandle(GTF_ICON_CLASS_HDL));
cls2Hnd = (CORINFO_CLASS_HANDLE)op2ClassFromHandle->gtIntCon.gtCompileTimeHandle;
}
else if (op2ClassFromHandle->OperGet() == GT_RUNTIMELOOKUP)
@@ -12455,9 +12452,34 @@ GenTree* Compiler::gtFoldTypeCompare(GenTree* tree)
cls2Hnd = op2ClassFromHandle->AsRuntimeLookup()->GetClassHandle();
runtimeLookupCount++;
}
+ // Tunnel through indirs we may see when prejitting
+ else if (op2ClassFromHandle->gtOper == GT_IND)
+ {
+ // The handle indirs we can optimize will be marked as non-faulting.
+ // Certain others (eg from refanytype) may not be.
+ if (op2ClassFromHandle->gtFlags & GTF_IND_NONFAULTING)
+ {
+ GenTree* op2HandleLiteral = op2ClassFromHandle->gtOp.gtOp1;
+
+ // If, after tunneling, we have a constant handle,
+ // remember the class and the value tree for later.
+ if ((op2HandleLiteral->gtOper == GT_CNS_INT) && (op2HandleLiteral->gtType == TYP_I_IMPL))
+ {
+ JITDUMP("tunneling through indir on op2\n");
+ op2TunneledHandle = op2HandleLiteral;
+
+ // These handle constants should be class handles.
+ assert(op2TunneledHandle->IsIconHandle(GTF_ICON_CLASS_HDL));
+ cls2Hnd = (CORINFO_CLASS_HANDLE)op2TunneledHandle->gtIntCon.gtCompileTimeHandle;
+ }
+ }
+ }
+ // If we have class handles, try and resolve the type equality test completely.
if ((cls1Hnd != nullptr) && (cls2Hnd != nullptr))
{
+ JITDUMP("Asking runtime to compare %p (%s) and %p (%s) for equality\n", cls1Hnd,
+ info.compCompHnd->getClassName(cls1Hnd), cls2Hnd, info.compCompHnd->getClassName(cls2Hnd));
TypeCompareState s = info.compCompHnd->compareTypesForEquality(cls1Hnd, cls2Hnd);
if (s != TypeCompareState::May)
@@ -12469,16 +12491,31 @@ GenTree* Compiler::gtFoldTypeCompare(GenTree* tree)
JITDUMP("Runtime reports comparison is known at jit time: %u\n", compareResult);
GenTree* result = gtNewIconNode(compareResult);
- // The runtime lookups are now dead code, so we may not
- // need the generic context kept alive either.
+ // Any runtime lookups that fed into this compare are
+ // now dead code, so they no longer require the runtime context.
assert(lvaGenericsContextUseCount >= runtimeLookupCount);
lvaGenericsContextUseCount -= runtimeLookupCount;
return result;
}
}
- // We can't answer definitively at jit time, but can still simplfy the comparison.
- GenTree* compare = gtNewOperNode(oper, TYP_INT, op1ClassFromHandle, op2ClassFromHandle);
+ JITDUMP("Could not find handle for %s%s\n", (cls1Hnd == nullptr) ? " cls1" : "",
+ (cls2Hnd == nullptr) ? " cls2" : "");
+
+ // We can't answer the equality comparison definitively at jit
+ // time, but can still simplfy the comparison.
+ //
+ // If we successfully tunneled through both operands, compare
+ // the tunnneled values, otherwise compare the orignal values.
+ GenTree* compare = nullptr;
+ if ((op1TunneledHandle != nullptr) && (op2TunneledHandle != nullptr))
+ {
+ compare = gtNewOperNode(oper, TYP_INT, op1TunneledHandle, op2TunneledHandle);
+ }
+ else
+ {
+ compare = gtNewOperNode(oper, TYP_INT, op1ClassFromHandle, op2ClassFromHandle);
+ }
// Drop any now-irrelvant flags
compare->gtFlags |= tree->gtFlags & (GTF_RELOP_JMP_USED | GTF_RELOP_QMARK | GTF_DONT_CSE);
@@ -12502,8 +12539,9 @@ GenTree* Compiler::gtFoldTypeCompare(GenTree* tree)
GenTree* const opOther = op1IsFromHandle ? op2 : op1;
// Tunnel through the handle operand to get at the class handle involved.
- GenTree* const opHandleArgument = opHandle->gtCall.gtCallArgs->gtOp.gtOp1;
- GenTree* opHandleLiteral = opHandleArgument;
+ GenTree* const opHandleArgument = opHandle->gtCall.gtCallArgs->gtOp.gtOp1;
+ GenTree* opHandleLiteral = opHandleArgument;
+ CORINFO_CLASS_HANDLE clsHnd = nullptr;
// Unwrap any GT_NOP node used to prevent constant folding
if ((opHandleLiteral->gtOper == GT_NOP) && (opHandleLiteral->gtType == TYP_I_IMPL))
@@ -12511,28 +12549,36 @@ GenTree* Compiler::gtFoldTypeCompare(GenTree* tree)
opHandleLiteral = opHandleLiteral->gtOp.gtOp1;
}
- // In the ngen case, we have to go thru an indirection to get the right handle.
- if (opHandleLiteral->gtOper == GT_IND)
+ // For runtime lookups we can get at the handle directly
+ if (opHandleLiteral->gtOper == GT_RUNTIMELOOKUP)
{
- // Handle indirs should be marked as nonfaulting.
- assert((opHandleLiteral->gtFlags & GTF_IND_NONFAULTING) != 0);
- opHandleLiteral = opHandleLiteral->gtOp.gtOp1;
+ clsHnd = opHandleLiteral->AsRuntimeLookup()->GetClassHandle();
+ }
+ else
+ {
+ // Tunnel through prejit indirs if necessary
+ if (opHandleLiteral->gtOper == GT_IND)
+ {
+ // Handle indirs should be marked as nonfaulting.
+ assert((opHandleLiteral->gtFlags & GTF_IND_NONFAULTING) != 0);
+ opHandleLiteral = opHandleLiteral->gtOp.gtOp1;
+ }
+
+ if ((opHandleLiteral->gtOper == GT_CNS_INT) && (opHandleLiteral->gtType == TYP_I_IMPL))
+ {
+ assert(opHandleLiteral->IsIconHandle(GTF_ICON_CLASS_HDL));
+ clsHnd = CORINFO_CLASS_HANDLE(opHandleLiteral->gtIntCon.gtCompileTimeHandle);
+ }
}
- // If, after tunneling, we don't have a constant handle, bail.
- if ((opHandleLiteral->gtOper != GT_CNS_INT) || (opHandleLiteral->gtType != TYP_I_IMPL))
+ // If we couldn't find the class handle, give up.
+ if (clsHnd == nullptr)
{
return tree;
}
- // We should have a class handle.
- assert(opHandleLiteral->IsIconHandle(GTF_ICON_CLASS_HDL));
-
- // Fetch the compile time handle, and use it to ask the VM if
- // this kind of type can be equality tested by a simple method
+ // Ask the VM if this type can be equality tested by a simple method
// table comparison.
- CORINFO_CLASS_HANDLE clsHnd = CORINFO_CLASS_HANDLE(opHandleLiteral->gtIntCon.gtCompileTimeHandle);
-
if (!info.compCompHnd->canInlineTypeCheckWithObjectVTable(clsHnd))
{
return tree;
@@ -12545,8 +12591,21 @@ GenTree* Compiler::gtFoldTypeCompare(GenTree* tree)
// opHandleArgument is the method table we're looking for.
GenTree* const knownMT = opHandleArgument;
- // Fetch object method table from the object itself
- GenTree* const objMT = gtNewOperNode(GT_IND, TYP_I_IMPL, opOther->gtUnOp.gtOp1);
+ // Fetch object method table from the object itself.
+ GenTree* objOp = nullptr;
+
+ // Note we may see intrinsified or regular calls to GetType
+ if (opOther->OperGet() == GT_INTRINSIC)
+ {
+ objOp = opOther->gtUnOp.gtOp1;
+ }
+ else
+ {
+ assert(opOther->OperGet() == GT_CALL);
+ objOp = opOther->gtCall.gtCallObjp;
+ }
+
+ GenTree* const objMT = gtNewOperNode(GT_IND, TYP_I_IMPL, objOp);
// Update various flags
objMT->gtFlags |= GTF_EXCEPT;
diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp
index 28f70cde46..98873bbe55 100644
--- a/src/jit/importer.cpp
+++ b/src/jit/importer.cpp
@@ -3281,32 +3281,84 @@ GenTreePtr Compiler::impInitializeArrayIntrinsic(CORINFO_SIG_INFO* sig)
true); // copyBlock
}
-/*****************************************************************************/
-// Returns the GenTree that should be used to do the intrinsic instead of the call.
-// Returns NULL if an intrinsic cannot be used
-
-GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis,
- CORINFO_CLASS_HANDLE clsHnd,
- CORINFO_METHOD_HANDLE method,
- CORINFO_SIG_INFO* sig,
- int memberRef,
- bool readonlyCall,
- bool tailCall,
- bool isJitIntrinsic,
- CorInfoIntrinsics* pIntrinsicID,
- bool* isSpecialIntrinsic)
+//------------------------------------------------------------------------
+// impIntrinsic: possibly expand intrinsic call into alternate IR sequence
+//
+// Arguments:
+// newobjThis - for constructor calls, the tree for the newly allocated object
+// clsHnd - handle for the intrinsic method's class
+// method - handle for the intrinsic method
+// sig - signature of the intrinsic method
+// memberRef - the token for the intrinsic method
+// readonlyCall - true if call has a readonly prefix
+// tailCall - true if call is in tail position
+// pConstrainedResolvedToken -- resolved token for constrained call, or nullptr
+// if call is not constrained
+// constraintCallThisTransform -- this transform to apply for a constrained call
+// isJitIntrinsic - true if method is a "new" jit intrinsic that the jit
+// must identify by name
+// pIntrinsicID [OUT] -- intrinsic ID (see enumeration in corinfo.h)
+// for "traditional" jit intrinsics
+// isSpecialIntrinsic [OUT] -- set true if intrinsic expansion is a call
+// that is amenable to special downstream optimization opportunities
+//
+// Returns:
+// IR tree to use in place of the call, or nullptr if the jit should treat
+// the intrinsic call like a normal call.
+//
+// pIntrinsicID set to non-illegal value if the call is recognized as a
+// traditional jit intrinsic, even if the intrinsic is not expaned.
+//
+// isSpecial set true if the expansion is subject to special
+// optimizations later in the jit processing
+//
+// Notes:
+// On success the IR tree may be a call to a different method or an inline
+// sequence. If it is a call, then the intrinsic processing here is responsible
+// for handling all the special cases, as upon return to impImportCall
+// expanded intrinsics bypass most of the normal call processing.
+//
+// Intrinsics are generally not recognized in minopts and debug codegen.
+//
+// However, certain traditional intrinsics are identifed as "must expand"
+// if there is no fallback implmentation to invoke; these must be handled
+// in all codegen modes.
+//
+// New style intrinsics (where the fallback implementation is in IL) are
+// identified as "must expand" if they are invoked from within their
+// own method bodies.
+//
+
+GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
+ CORINFO_CLASS_HANDLE clsHnd,
+ CORINFO_METHOD_HANDLE method,
+ CORINFO_SIG_INFO* sig,
+ int memberRef,
+ bool readonlyCall,
+ bool tailCall,
+ CORINFO_RESOLVED_TOKEN* pConstrainedResolvedToken,
+ CORINFO_THIS_TRANSFORM constraintCallThisTransform,
+ bool isJitIntrinsic,
+ CorInfoIntrinsics* pIntrinsicID,
+ bool* isSpecialIntrinsic)
{
bool mustExpand = false;
bool isSpecial = false;
CorInfoIntrinsics intrinsicID = info.compCompHnd->getIntrinsicID(method, &mustExpand);
*pIntrinsicID = intrinsicID;
- // Jit intrinsics are always optional to expand, and won't have an
- // Intrinsic ID.
+ // Jit intrinsics won't have an IntrinsicID, and won't be identifed
+ // by the VM as must-expand.
if (isJitIntrinsic)
{
assert(!mustExpand);
assert(intrinsicID == CORINFO_INTRINSIC_Illegal);
+
+ // They however may still be must-expand. The convention we
+ // have adopted is that if we are compiling the intrinsic and
+ // it calls itself recursively, the recursive call is
+ // must-expand.
+ mustExpand = gtIsRecursiveCall(method);
}
#ifndef _TARGET_ARM_
@@ -3330,21 +3382,13 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis,
GenTreePtr retNode = nullptr;
- //
- // We disable the inlining of intrinsic for MinOpts,
- // but we should always expand hardware intrinsics whose managed method body
- // is a directly recursive call site. This design makes hardware intrinsic
- // be able to work with debugger and reflection.
- if (!mustExpand && (opts.compDbgCode || opts.MinOpts()) && !gtIsRecursiveCall(method))
+ // Under debug and minopts, only expand what is required.
+ if (!mustExpand && (opts.compDbgCode || opts.MinOpts()))
{
*pIntrinsicID = CORINFO_INTRINSIC_Illegal;
return retNode;
}
- // Currently we don't have CORINFO_INTRINSIC_Exp because it does not
- // seem to work properly for Infinity values, we don't do
- // CORINFO_INTRINSIC_Pow because it needs a Helper which we currently don't have
-
var_types callType = JITtype2varType(sig->retType);
/* First do the intrinsics which are always smaller than a call */
@@ -3534,43 +3578,74 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis,
#ifndef LEGACY_BACKEND
case CORINFO_INTRINSIC_Object_GetType:
{
- op1 = impPopStack().val;
+ JITDUMP("\n impIntrinsic: call to Object.GetType\n");
+ op1 = impStackTop(0).val;
// If we're calling GetType on a boxed value, just get the type directly.
- if (!opts.MinOpts() && !opts.compDbgCode)
+ if (op1->IsBoxedValue())
{
- if (op1->IsBoxedValue())
+ JITDUMP("Attempting to optimize box(...).getType() to direct type construction\n");
+
+ // Try and clean up the box. Obtain the handle we
+ // were going to pass to the newobj.
+ GenTree* boxTypeHandle = gtTryRemoveBoxUpstreamEffects(op1, BR_REMOVE_AND_NARROW_WANT_TYPE_HANDLE);
+
+ if (boxTypeHandle != nullptr)
{
- JITDUMP("Attempting to optimize box(...).getType() to direct type construction\n");
+ // Note we don't need to play the TYP_STRUCT games here like
+ // do for LDTOKEN since the return value of this operator is Type,
+ // not RuntimeTypeHandle.
+ impPopStack();
+ GenTreeArgList* helperArgs = gtNewArgList(boxTypeHandle);
+ GenTree* runtimeType =
+ gtNewHelperCallNode(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE, TYP_REF, helperArgs);
+ retNode = runtimeType;
+ }
+ }
- // Try and clean up the box. Obtain the handle we
- // were going to pass to the newobj.
- GenTree* boxTypeHandle = gtTryRemoveBoxUpstreamEffects(op1, BR_REMOVE_AND_NARROW_WANT_TYPE_HANDLE);
+ // If we have a constrained callvirt with a "box this" transform
+ // we know we have a value class and hence an exact type.
+ //
+ // If so, instead of boxing and then extracting the type, just
+ // construct the type directly.
+ if ((retNode == nullptr) && (pConstrainedResolvedToken != nullptr) &&
+ (constraintCallThisTransform == CORINFO_BOX_THIS))
+ {
+ // Ensure this is one of the is simple box cases (in particular, rule out nullables).
+ const CorInfoHelpFunc boxHelper = info.compCompHnd->getBoxHelper(pConstrainedResolvedToken->hClass);
+ const bool isSafeToOptimize = (boxHelper == CORINFO_HELP_BOX);
- if (boxTypeHandle != nullptr)
- {
- // Note we don't need to play the TYP_STRUCT games here like
- // do for LDTOKEN since the return value of this operator is Type,
- // not RuntimeTypeHandle.
- GenTreeArgList* helperArgs = gtNewArgList(boxTypeHandle);
- GenTree* runtimeType =
- gtNewHelperCallNode(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE, TYP_REF, helperArgs);
- retNode = runtimeType;
+ if (isSafeToOptimize)
+ {
+ JITDUMP("Optimizing constrained box-this obj.getType() to direct type construction\n");
+ impPopStack();
+ GenTree* typeHandleOp =
+ impTokenToHandle(pConstrainedResolvedToken, nullptr, TRUE /* mustRestoreHandle */);
+ GenTreeArgList* helperArgs = gtNewArgList(typeHandleOp);
+ GenTree* runtimeType =
+ gtNewHelperCallNode(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE, TYP_REF, helperArgs);
+ retNode = runtimeType;
+ }
+ }
#ifdef DEBUG
- JITDUMP("Optimized; result is\n");
- if (verbose)
- {
- gtDispTree(retNode);
- }
-#endif
- }
+ if (retNode != nullptr)
+ {
+ JITDUMP("Optimized result for call to GetType is\n");
+ if (verbose)
+ {
+ gtDispTree(retNode);
}
}
+#endif
- // Else expand as an intrinsic
- if (retNode == nullptr)
+ // Else expand as an intrinsic, unless the call is constrained,
+ // in which case we defer expansion to allow impImportCall do the
+ // special constraint processing.
+ if ((retNode == nullptr) && (pConstrainedResolvedToken == nullptr))
{
+ JITDUMP("Expanding as special intrinsic\n");
+ impPopStack();
op1 = new (this, GT_INTRINSIC) GenTreeIntrinsic(genActualType(callType), op1, intrinsicID, method);
// Set the CALL flag to indicate that the operator is implemented by a call.
@@ -3578,11 +3653,20 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis,
// CORINFO_INTRINSIC_Object_GetType intrinsic can throw NullReferenceException.
op1->gtFlags |= (GTF_CALL | GTF_EXCEPT);
retNode = op1;
- // Might be further optimizable during morph
+ // Might be further optimizable, so arrange to leave a mark behind
+ isSpecial = true;
+ }
+
+ if (retNode == nullptr)
+ {
+ JITDUMP("Leaving as normal call\n");
+ // Might be further optimizable, so arrange to leave a mark behind
isSpecial = true;
}
+
break;
}
+
#endif
// Implement ByReference Ctor. This wraps the assignment of the ref into a byref-like field
// in a value type. The canonical example of this is Span<T>. In effect this is just a
@@ -7025,11 +7109,13 @@ var_types Compiler::impImportCall(OPCODE opcode,
// <NICE> Factor this into getCallInfo </NICE>
const bool isIntrinsic = (mflags & CORINFO_FLG_INTRINSIC) != 0;
const bool isJitIntrinsic = (mflags & CORINFO_FLG_JIT_INTRINSIC) != 0;
+ const bool isTail = canTailCall && (tailCall != 0);
bool isSpecialIntrinsic = false;
- if ((isIntrinsic || isJitIntrinsic) && !pConstrainedResolvedToken)
+ if (isIntrinsic || isJitIntrinsic)
{
- call = impIntrinsic(newobjThis, clsHnd, methHnd, sig, pResolvedToken->token, readonlyCall,
- (canTailCall && (tailCall != 0)), isJitIntrinsic, &intrinsicID, &isSpecialIntrinsic);
+ call = impIntrinsic(newobjThis, clsHnd, methHnd, sig, pResolvedToken->token, readonlyCall, isTail,
+ pConstrainedResolvedToken, callInfo->thisTransform, isJitIntrinsic, &intrinsicID,
+ &isSpecialIntrinsic);
if (compDonotInline())
{
diff --git a/tests/src/JIT/opt/Types/Equality.cs b/tests/src/JIT/opt/Types/Equality.cs
new file mode 100644
index 0000000000..dea3e3073d
--- /dev/null
+++ b/tests/src/JIT/opt/Types/Equality.cs
@@ -0,0 +1,90 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Optimization of type equality tests
+
+using System;
+
+struct Wrap1<T> {}
+
+struct Wrap2<T> {}
+
+class EqualityTests
+{
+ static bool IsInt<T>()
+ {
+ return (typeof(T) == typeof(int));
+ }
+
+ static bool IsInt<T>(T t)
+ {
+ return (t.GetType() == typeof(int));
+ }
+
+ static bool IsString<T>()
+ {
+ return (typeof(T) == typeof(string));
+ }
+
+ static bool IsString<T>(T t)
+ {
+ return (t.GetType() == typeof(string));
+ }
+
+ static bool IsIntArray<T>()
+ {
+ return (typeof(T) == typeof(int[]));
+ }
+
+ static bool IsStringArray<T>()
+ {
+ return (typeof(T) == typeof(string[]));
+ }
+
+ static bool IsWrap1<T,U>()
+ {
+ return (typeof(U) == typeof(Wrap1<T>));
+ }
+
+ static bool IsWrap1<T,U>(U u)
+ {
+ return (u.GetType() == typeof(Wrap1<T>));
+ }
+
+ public static int Main()
+ {
+ // Fully optimized
+ bool c1 = IsInt<int>();
+ bool c2 = IsInt<string>();
+ bool c3 = IsString<int>();
+
+ // Partially optimized (method table check)
+ bool c4 = IsString<string>();
+
+ // Fully optimized
+ bool d1 = IsInt<int>(3);
+ bool d3 = IsString<int>(3);
+
+ // Partially optimized (method table check)
+ bool d2 = IsInt<string>("three");
+ bool d4 = IsString<string>("three");
+
+ // Partially optimized (runtime lookup)
+ bool e1 = IsIntArray<int[]>();
+ bool e2 = IsIntArray<string[]>();
+ bool e3 = IsStringArray<int[]>();
+ bool e4 = IsStringArray<string[]>();
+
+ // Fully optimized
+ bool f1 = IsWrap1<int, Wrap1<int>>();
+ bool f2 = IsWrap1<int, Wrap2<int>>();
+ bool f3 = IsWrap1<int, Wrap2<int>>(new Wrap2<int>());
+ bool f4 = IsWrap1<int, Wrap1<int>>(new Wrap1<int>());
+
+ bool pos = c1 & c4 & d1 & d4 & e1 & e4 & f1 & f4;
+ bool neg = c2 & c3 & d2 & d3 & e2 & e3 & f2 & f3;
+
+ return pos & !neg ? 100 : 0;
+ }
+}
diff --git a/tests/src/JIT/opt/Types/Equality.csproj b/tests/src/JIT/opt/Types/Equality.csproj
new file mode 100644
index 0000000000..8c06adc777
--- /dev/null
+++ b/tests/src/JIT/opt/Types/Equality.csproj
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <AssemblyName>$(MSBuildProjectName)</AssemblyName>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{95DFC527-4DC1-495E-97D7-E94EE1F7140D}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT .0\UITestExtensionPackages</ReferencePath>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
+ <NuGetPackageImportStamp>7a9bfb7d</NuGetPackageImportStamp>
+ <CLRTestPriority>0</CLRTestPriority>
+ </PropertyGroup>
+ <!-- Default configurations to help VS understand the configurations -->
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "></PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "></PropertyGroup>
+ <ItemGroup>
+ <CodeAnalysisDependentAssemblyPaths Condition=" '$(VS100COMNTOOLS)' != '' " Include="$(VS100COMNTOOLS)..\IDE\PrivateAssemblies">
+ <Visible>False</Visible>
+ </CodeAnalysisDependentAssemblyPaths>
+ </ItemGroup>
+ <PropertyGroup>
+ <DebugType>PdbOnly</DebugType>
+ <Optimize>True</Optimize>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="Equality.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+ </ItemGroup>
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
+ <PropertyGroup Condition=" '$(MsBuildProjectDirOverride)' != '' "></PropertyGroup>
+</Project> \ No newline at end of file