summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Ayers <andya@microsoft.com>2017-09-11 11:12:45 -0700
committerGitHub <noreply@github.com>2017-09-11 11:12:45 -0700
commite764b4dd021a38ba4c55a806069c4856e56e3f45 (patch)
tree54e674dee207120a4d26ec01f933a1759cbec6d4
parent354211fb52ae1503172ba0b1b44ff217ad712dbc (diff)
downloadcoreclr-e764b4dd021a38ba4c55a806069c4856e56e3f45.tar.gz
coreclr-e764b4dd021a38ba4c55a806069c4856e56e3f45.tar.bz2
coreclr-e764b4dd021a38ba4c55a806069c4856e56e3f45.zip
JIT: optimize Enum.HasFlag (#13748)
Check for calls to `Enum.HasFlag` using the new named intrinsic support introduced in #13815. Implement a simple recognizer for these named intrinsics (currently just recognizing `Enum.HasFlag`). When the call is recognized, optimize if both operands are boxes with compatible types and both boxes can be removed. The optimization changes the call to a simple and/compare tree on the underlying enum values. To accomplish this, generalize the behavior of `gtTryRemoveBoxUpstreamEffects` to add a "trial removal" mode and to optionally suppress narrowing of the copy source. Invoke the optimization during importation (which will catch most cases) and again during morph (to get the post-inline cases). Added test cases. Suprisingly there were almost no uses of HasFlag in the current CoreCLR test suite. Closes #5626.
-rw-r--r--src/jit/compiler.h17
-rw-r--r--src/jit/gentree.cpp225
-rw-r--r--src/jit/importer.cpp105
-rw-r--r--src/jit/morph.cpp22
-rw-r--r--src/jit/namedintrinsiclist.h16
-rw-r--r--tests/src/JIT/opt/Enum/hasflag.cs169
-rw-r--r--tests/src/JIT/opt/Enum/hasflag.csproj39
-rw-r--r--tests/src/JIT/opt/Enum/shared.cs41
-rw-r--r--tests/src/JIT/opt/Enum/shared.csproj39
9 files changed, 628 insertions, 45 deletions
diff --git a/src/jit/compiler.h b/src/jit/compiler.h
index 01997f1665..c2ecb0dbae 100644
--- a/src/jit/compiler.h
+++ b/src/jit/compiler.h
@@ -45,6 +45,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
#include "valuenum.h"
#include "reglist.h"
#include "jittelemetry.h"
+#include "namedintrinsiclist.h"
#ifdef LATE_DISASM
#include "disasm.h"
#endif
@@ -2237,7 +2238,17 @@ public:
gtFoldExprConst(GenTreePtr tree);
GenTreePtr gtFoldExprSpecial(GenTreePtr tree);
GenTreePtr gtFoldExprCompare(GenTreePtr tree);
- bool gtTryRemoveBoxUpstreamEffects(GenTreePtr tree);
+
+ // Options to control behavior of gtTryRemoveBoxUpstreamEffects
+ enum BoxRemovalOptions
+ {
+ BR_REMOVE_AND_NARROW, // remove effects and minimize remaining work
+ BR_REMOVE_BUT_NOT_NARROW, // remove effects but leave box copy source full size
+ BR_DONT_REMOVE // just check if removal is possible
+ };
+
+ GenTree* gtTryRemoveBoxUpstreamEffects(GenTree* tree, BoxRemovalOptions options = BR_REMOVE_AND_NARROW);
+ GenTree* gtOptimizeEnumHasFlag(GenTree* thisOp, GenTree* flagOp);
//-------------------------------------------------------------------------
// Get the handle, if any.
@@ -2981,7 +2992,9 @@ protected:
bool readonlyCall,
bool tailCall,
bool isJitIntrinsic,
- CorInfoIntrinsics* pIntrinsicID);
+ CorInfoIntrinsics* pIntrinsicID,
+ bool* isSpecialIntrinsic = nullptr);
+ NamedIntrinsic lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method);
GenTreePtr impArrayAccessIntrinsic(CORINFO_CLASS_HANDLE clsHnd,
CORINFO_SIG_INFO* sig,
int memberRef,
diff --git a/src/jit/gentree.cpp b/src/jit/gentree.cpp
index 19dfdbd235..9ae0ca5003 100644
--- a/src/jit/gentree.cpp
+++ b/src/jit/gentree.cpp
@@ -12209,7 +12209,8 @@ GenTreePtr Compiler::gtFoldExprSpecial(GenTreePtr tree)
assert(!gtTreeHasSideEffects(op->gtBox.gtOp.gtOp1, GTF_SIDE_EFFECT));
// See if we can optimize away the box and related statements.
- bool didOptimize = gtTryRemoveBoxUpstreamEffects(op);
+ GenTree* boxSourceTree = gtTryRemoveBoxUpstreamEffects(op);
+ bool didOptimize = (boxSourceTree != nullptr);
// If optimization succeeded, remove the box.
if (didOptimize)
@@ -12452,18 +12453,30 @@ DONE_FOLD:
// the copy.
//
// Arguments:
-// op -- the box node to optimize
+// op - the box node to optimize
+// options - controls whether and how trees are modified
+// (see notes)
//
// Return Value:
-// True if the upstream effects were removed. Note parts of the
-// copy tree may remain, if the copy source had side effects.
-//
-// False if the upstream effects could not be removed.
+// A tree representing the original value to box, if removal
+// is successful/possible (but see note). nullptr if removal fails.
//
// Notes:
// Value typed box gets special treatment because it has associated
// side effects that can be removed if the box result is not used.
//
+// By default (options == BR_REMOVE_AND_NARROW) this method will
+// try and remove unnecessary trees and will try and reduce remaning
+// operations to the minimal set, possibly narrowing the width of
+// loads from the box source if it is a struct.
+//
+// To perform a trial removal, pass BR_DONT_REMOVE. This can be
+// useful to determine if this optimization should only be
+// performed if some other conditions hold true.
+//
+// To remove but not alter the access to the box source, pass
+// BR_REMOVE_BUT_NOT_NARROW.
+//
// If removal fails, is is possible that a subsequent pass may be
// able to optimize. Blocking side effects may now be minimized
// (null or bounds checks might have been removed) or might be
@@ -12471,21 +12484,22 @@ DONE_FOLD:
// return expression). So the box is perhaps best left as is to
// help trigger this re-examination.
-bool Compiler::gtTryRemoveBoxUpstreamEffects(GenTreePtr op)
+GenTree* Compiler::gtTryRemoveBoxUpstreamEffects(GenTree* op, BoxRemovalOptions options)
{
JITDUMP("gtTryRemoveBoxUpstreamEffects called for [%06u]\n", dspTreeID(op));
assert(op->IsBoxedValue());
// grab related parts for the optimization
- GenTreePtr asgStmt = op->gtBox.gtAsgStmtWhenInlinedBoxValue;
+ GenTree* asgStmt = op->gtBox.gtAsgStmtWhenInlinedBoxValue;
assert(asgStmt->gtOper == GT_STMT);
- GenTreePtr copyStmt = op->gtBox.gtCopyStmtWhenInlinedBoxValue;
+ GenTree* copyStmt = op->gtBox.gtCopyStmtWhenInlinedBoxValue;
assert(copyStmt->gtOper == GT_STMT);
#ifdef DEBUG
if (verbose)
{
- printf("\nAttempting to remove side effects of BOX (valuetype)\n");
+ printf("\n%s to remove side effects of BOX (valuetype)\n",
+ options == BR_DONT_REMOVE ? "Checking if it is possible" : "Attempting");
gtDispTree(op);
printf("\nWith assign\n");
gtDispTree(asgStmt);
@@ -12495,15 +12509,15 @@ bool Compiler::gtTryRemoveBoxUpstreamEffects(GenTreePtr op)
#endif
// If we don't recognize the form of the assign, bail.
- GenTreePtr asg = asgStmt->gtStmt.gtStmtExpr;
+ GenTree* asg = asgStmt->gtStmt.gtStmtExpr;
if (asg->gtOper != GT_ASG)
{
JITDUMP(" bailing; unexpected assignment op %s\n", GenTree::OpName(asg->gtOper));
- return false;
+ return nullptr;
}
// If we don't recognize the form of the copy, bail.
- GenTreePtr copy = copyStmt->gtStmt.gtStmtExpr;
+ GenTree* copy = copyStmt->gtStmt.gtStmtExpr;
if (copy->gtOper != GT_ASG)
{
// GT_RET_EXPR is a tolerable temporary failure.
@@ -12521,18 +12535,18 @@ bool Compiler::gtTryRemoveBoxUpstreamEffects(GenTreePtr op)
// looking for.
JITDUMP(" bailing; unexpected copy op %s\n", GenTree::OpName(copy->gtOper));
}
- return false;
+ return nullptr;
}
// If the copy is a struct copy, make sure we know how to isolate
// any source side effects.
- GenTreePtr copySrc = copy->gtOp.gtOp2;
+ GenTree* copySrc = copy->gtOp.gtOp2;
// If the copy source is from a pending inline, wait for it to resolve.
if (copySrc->gtOper == GT_RET_EXPR)
{
JITDUMP(" bailing; must wait for replacement of copy source %s\n", GenTree::OpName(copySrc->gtOper));
- return false;
+ return nullptr;
}
bool hasSrcSideEffect = false;
@@ -12551,12 +12565,18 @@ bool Compiler::gtTryRemoveBoxUpstreamEffects(GenTreePtr op)
// We don't know how to handle other cases, yet.
JITDUMP(" bailing; unexpected copy source struct op with side effect %s\n",
GenTree::OpName(copySrc->gtOper));
- return false;
+ return nullptr;
}
}
}
- // Proceed with the optimization
+ // If this was a trial removal, we're done.
+ if (options == BR_DONT_REMOVE)
+ {
+ return copySrc;
+ }
+
+ // Otherwise, proceed with the optimization.
//
// Change the assignment expression to a NOP.
JITDUMP("\nBashing NEWOBJ [%06u] to NOP\n", dspTreeID(asg));
@@ -12588,10 +12608,18 @@ bool Compiler::gtTryRemoveBoxUpstreamEffects(GenTreePtr op)
// source struct; there's no need to read the
// entire thing, and no place to put it.
assert(copySrc->gtOper == GT_OBJ || copySrc->gtOper == GT_IND || copySrc->gtOper == GT_FIELD);
- copySrc->ChangeOper(GT_IND);
- copySrc->gtType = TYP_BYTE;
copyStmt->gtStmt.gtStmtExpr = copySrc;
- JITDUMP(" to read first byte of struct via modified [%06u]\n", dspTreeID(copySrc));
+
+ if (options == BR_REMOVE_AND_NARROW)
+ {
+ JITDUMP(" to read first byte of struct via modified [%06u]\n", dspTreeID(copySrc));
+ copySrc->ChangeOper(GT_IND);
+ copySrc->gtType = TYP_BYTE;
+ }
+ else
+ {
+ JITDUMP(" to read entire struct via modified [%06u]\n", dspTreeID(copySrc));
+ }
}
if (fgStmtListThreaded)
@@ -12600,8 +12628,159 @@ bool Compiler::gtTryRemoveBoxUpstreamEffects(GenTreePtr op)
fgSetStmtSeq(copyStmt);
}
- // Box effects were successfully optimized
- return true;
+ // Box effects were successfully optimized.
+ return copySrc;
+}
+
+//------------------------------------------------------------------------
+// gtOptimizeEnumHasFlag: given the operands for a call to Enum.HasFlag,
+// try and optimize the call to a simple and/compare tree.
+//
+// Arguments:
+// thisOp - first argument to the call
+// flagOp - second argument to the call
+//
+// Return Value:
+// A new cmp/amd tree if successful. nullptr on failure.
+//
+// Notes:
+// If successful, may allocate new temps and modify connected
+// statements.
+
+GenTree* Compiler::gtOptimizeEnumHasFlag(GenTree* thisOp, GenTree* flagOp)
+{
+ JITDUMP("Considering optimizing call to Enum.HasFlag....\n");
+
+ // Operands must be boxes
+ if (!thisOp->IsBoxedValue() || !flagOp->IsBoxedValue())
+ {
+ JITDUMP("bailing, need both inputs to be BOXes\n");
+ return nullptr;
+ }
+
+ // Operands must have same type
+ bool isExactThis = false;
+ bool isNonNullThis = false;
+ CORINFO_CLASS_HANDLE thisHnd = gtGetClassHandle(thisOp, &isExactThis, &isNonNullThis);
+
+ if (thisHnd == nullptr)
+ {
+ JITDUMP("bailing, can't find type for 'this' operand\n");
+ return nullptr;
+ }
+
+ // A boxed thisOp should have exact type and non-null instance
+ assert(isExactThis);
+ assert(isNonNullThis);
+
+ bool isExactFlag = false;
+ bool isNonNullFlag = false;
+ CORINFO_CLASS_HANDLE flagHnd = gtGetClassHandle(flagOp, &isExactFlag, &isNonNullFlag);
+
+ if (flagHnd == nullptr)
+ {
+ JITDUMP("bailing, can't find type for 'flag' operand\n");
+ return nullptr;
+ }
+
+ // A boxed flagOp should have exact type and non-null instance
+ assert(isExactFlag);
+ assert(isNonNullFlag);
+
+ if (flagHnd != thisHnd)
+ {
+ JITDUMP("bailing, operand types differ\n");
+ return nullptr;
+ }
+
+ // If we have a shared type instance we can't safely check type
+ // equality, so bail.
+ DWORD classAttribs = info.compCompHnd->getClassAttribs(thisHnd);
+ if (classAttribs & CORINFO_FLG_SHAREDINST)
+ {
+ JITDUMP("bailing, have shared instance type\n");
+ return nullptr;
+ }
+
+ // Simulate removing the box for thisOP. We need to know that it can
+ // be safely removed before we can optimize.
+ GenTree* thisVal = gtTryRemoveBoxUpstreamEffects(thisOp, BR_DONT_REMOVE);
+ if (thisVal == nullptr)
+ {
+ // Note we may fail here if the this operand comes from
+ // a call. We should be able to retry this post-inlining.
+ JITDUMP("bailing, can't undo box of 'this' operand\n");
+ return nullptr;
+ }
+
+ GenTree* flagVal = gtTryRemoveBoxUpstreamEffects(flagOp, BR_REMOVE_BUT_NOT_NARROW);
+ if (flagVal == nullptr)
+ {
+ // Note we may fail here if the flag operand comes from
+ // a call. We should be able to retry this post-inlining.
+ JITDUMP("bailing, can't undo box of 'flag' operand\n");
+ return nullptr;
+ }
+
+ // Yes, both boxes can be cleaned up. Optimize.
+ JITDUMP("Optimizing call to Enum.HasFlag\n");
+
+ // Undo the boxing of thisOp and prepare to operate directly
+ // on the original enum values.
+ thisVal = gtTryRemoveBoxUpstreamEffects(thisOp, BR_REMOVE_BUT_NOT_NARROW);
+
+ // Our trial removal above should guarantee successful removal here.
+ assert(thisVal != nullptr);
+
+ // We should have a consistent view of the type
+ var_types type = thisVal->TypeGet();
+ assert(type == flagVal->TypeGet());
+
+ // The thisVal and flagVal trees come from earlier statements.
+ //
+ // Unless they are invariant values, we need to evaluate them both
+ // to temps at those points to safely transmit the values here.
+ //
+ // Also we need to use the flag twice, so we need two trees for it.
+ GenTree* thisValOpt = nullptr;
+ GenTree* flagValOpt = nullptr;
+ GenTree* flagValOptCopy = nullptr;
+
+ if (thisVal->IsIntegralConst())
+ {
+ thisValOpt = gtClone(thisVal);
+ }
+ else
+ {
+ const unsigned thisTmp = lvaGrabTemp(true DEBUGARG("Enum:HasFlag this temp"));
+ GenTree* thisAsg = gtNewTempAssign(thisTmp, thisVal);
+ GenTree* thisAsgStmt = thisOp->AsBox()->gtCopyStmtWhenInlinedBoxValue;
+ thisAsgStmt->gtStmt.gtStmtExpr = thisAsg;
+ thisValOpt = gtNewLclvNode(thisTmp, type);
+ }
+
+ if (flagVal->IsIntegralConst())
+ {
+ flagValOpt = gtClone(flagVal);
+ flagValOptCopy = gtClone(flagVal);
+ }
+ else
+ {
+ const unsigned flagTmp = lvaGrabTemp(true DEBUGARG("Enum:HasFlag flag temp"));
+ GenTree* flagAsg = gtNewTempAssign(flagTmp, flagVal);
+ GenTree* flagAsgStmt = flagOp->AsBox()->gtCopyStmtWhenInlinedBoxValue;
+ flagAsgStmt->gtStmt.gtStmtExpr = flagAsg;
+ flagValOpt = gtNewLclvNode(flagTmp, type);
+ flagValOptCopy = gtNewLclvNode(flagTmp, type);
+ }
+
+ // Turn the call into (thisValTmp & flagTmp) == flagTmp.
+ GenTree* andTree = gtNewOperNode(GT_AND, type, thisValOpt, flagValOpt);
+ GenTree* cmpTree = gtNewOperNode(GT_EQ, TYP_INT, andTree, flagValOptCopy);
+
+ JITDUMP("Optimized call to Enum.HasFlag\n");
+
+ return cmpTree;
}
/*****************************************************************************
diff --git a/src/jit/importer.cpp b/src/jit/importer.cpp
index dab2ee30a3..42bb84d93a 100644
--- a/src/jit/importer.cpp
+++ b/src/jit/importer.cpp
@@ -3282,9 +3282,11 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis,
bool readonlyCall,
bool tailCall,
bool isJitIntrinsic,
- CorInfoIntrinsics* pIntrinsicID)
+ CorInfoIntrinsics* pIntrinsicID,
+ bool* isSpecialIntrinsic)
{
bool mustExpand = false;
+ bool isSpecial = false;
CorInfoIntrinsics intrinsicID = info.compCompHnd->getIntrinsicID(method, &mustExpand);
*pIntrinsicID = intrinsicID;
@@ -3617,6 +3619,9 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis,
// CORINFO_INTRINSIC_Object_GetType intrinsic can throw NullReferenceException.
op1->gtFlags |= (GTF_CALL | GTF_EXCEPT);
retNode = op1;
+
+ // Might be optimizable during morph
+ isSpecial = true;
break;
#endif
// Implement ByReference Ctor. This wraps the assignment of the ref into a byref-like field
@@ -3759,33 +3764,55 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis,
break;
}
+ case CORINFO_INTRINSIC_TypeEQ:
+ case CORINFO_INTRINSIC_TypeNEQ:
+ case CORINFO_INTRINSIC_GetCurrentManagedThread:
+ case CORINFO_INTRINSIC_GetManagedThreadId:
+ {
+ // Retry optimizing these during morph
+ isSpecial = true;
+ break;
+ }
+
default:
/* Unknown intrinsic */
break;
}
-#ifdef DEBUG
- // Sample code showing how to use the new intrinsic mechansim.
+ // Look for new-style jit intrinsics by name
if (isJitIntrinsic)
{
assert(retNode == nullptr);
- const char* className = nullptr;
- const char* namespaceName = nullptr;
- const char* methodName = info.compCompHnd->getMethodNameFromMetadata(method, &className, &namespaceName);
+ const NamedIntrinsic ni = lookupNamedIntrinsic(method);
- if ((namespaceName != nullptr) && strcmp(namespaceName, "System") == 0)
+ switch (ni)
{
- if ((className != nullptr) && strcmp(className, "Enum") == 0)
+ case NI_Enum_HasFlag:
{
- if ((methodName != nullptr) && strcmp(methodName, "HasFlag") == 0)
+ GenTree* thisOp = impStackTop(1).val;
+ GenTree* flagOp = impStackTop(0).val;
+ GenTree* optTree = gtOptimizeEnumHasFlag(thisOp, flagOp);
+
+ if (optTree != nullptr)
+ {
+ // Optimization successful. Pop the stack for real.
+ impPopStack();
+ impPopStack();
+ retNode = optTree;
+ }
+ else
{
- // Todo: plug in the intrinsic expansion
- JITDUMP("Found Intrinsic call to Enum.HasFlag\n");
+ // Retry optimizing this during morph.
+ isSpecial = true;
}
+
+ break;
}
+
+ default:
+ break;
}
}
-#endif
if (mustExpand)
{
@@ -3795,9 +3822,52 @@ GenTreePtr Compiler::impIntrinsic(GenTreePtr newobjThis,
}
}
+ // Optionally report if this intrinsic is special
+ // (that is, potentially re-optimizable during morph).
+ if (isSpecialIntrinsic != nullptr)
+ {
+ *isSpecialIntrinsic = isSpecial;
+ }
+
return retNode;
}
+//------------------------------------------------------------------------
+// lookupNamedIntrinsic: map method to jit named intrinsic value
+//
+// Arguments:
+// method -- method handle for method
+//
+// Return Value:
+// Id for the named intrinsic, or Illegal if none.
+//
+// Notes:
+// method should have CORINFO_FLG_JIT_INTRINSIC set in its attributes,
+// otherwise it is not a named jit intrinsic.
+//
+
+NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
+{
+ NamedIntrinsic result = NI_Illegal;
+
+ const char* className = nullptr;
+ const char* namespaceName = nullptr;
+ const char* methodName = info.compCompHnd->getMethodNameFromMetadata(method, &className, &namespaceName);
+
+ if ((namespaceName != nullptr) && strcmp(namespaceName, "System") == 0)
+ {
+ if ((className != nullptr) && strcmp(className, "Enum") == 0)
+ {
+ if ((methodName != nullptr) && strcmp(methodName, "HasFlag") == 0)
+ {
+ result = NI_Enum_HasFlag;
+ }
+ }
+ }
+
+ return result;
+}
+
/*****************************************************************************/
GenTreePtr Compiler::impArrayAccessIntrinsic(
@@ -6808,12 +6878,13 @@ var_types Compiler::impImportCall(OPCODE opcode,
#endif // DEBUG
// <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 isIntrinsic = (mflags & CORINFO_FLG_INTRINSIC) != 0;
+ const bool isJitIntrinsic = (mflags & CORINFO_FLG_JIT_INTRINSIC) != 0;
+ bool isSpecialIntrinsic = false;
if ((isIntrinsic || isJitIntrinsic) && !pConstrainedResolvedToken)
{
call = impIntrinsic(newobjThis, clsHnd, methHnd, sig, pResolvedToken->token, readonlyCall,
- (canTailCall && (tailCall != 0)), isJitIntrinsic, &intrinsicID);
+ (canTailCall && (tailCall != 0)), isJitIntrinsic, &intrinsicID, &isSpecialIntrinsic);
if (compIsForInlining() && compInlineResult->IsFailure())
{
@@ -7129,9 +7200,7 @@ var_types Compiler::impImportCall(OPCODE opcode,
}
// Mark call if it's one of the ones we will maybe treat as an intrinsic
- if (intrinsicID == CORINFO_INTRINSIC_Object_GetType || intrinsicID == CORINFO_INTRINSIC_TypeEQ ||
- intrinsicID == CORINFO_INTRINSIC_TypeNEQ || intrinsicID == CORINFO_INTRINSIC_GetCurrentManagedThread ||
- intrinsicID == CORINFO_INTRINSIC_GetManagedThreadId)
+ if (isSpecialIntrinsic)
{
call->gtCall.gtCallMoreFlags |= GTF_CALL_M_SPECIAL_INTRINSIC;
}
diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp
index 09e200a92c..0de22ca18d 100644
--- a/src/jit/morph.cpp
+++ b/src/jit/morph.cpp
@@ -8840,12 +8840,30 @@ NO_TAIL_CALL:
compCurBB->bbFlags |= BBF_GC_SAFE_POINT;
}
- // Morph Type.op_Equality and Type.op_Inequality
- // We need to do this before the arguments are morphed
+ // Morph Type.op_Equality, Type.op_Inequality, and Enum.HasFlag
+ //
+ // We need to do these before the arguments are morphed
if ((call->gtCallMoreFlags & GTF_CALL_M_SPECIAL_INTRINSIC))
{
CorInfoIntrinsics methodID = info.compCompHnd->getIntrinsicID(call->gtCallMethHnd);
+ if (methodID == CORINFO_INTRINSIC_Illegal)
+ {
+ // Check for a new-style jit intrinsic.
+ const NamedIntrinsic ni = lookupNamedIntrinsic(call->gtCallMethHnd);
+
+ if (ni == NI_Enum_HasFlag)
+ {
+ GenTree* thisOp = call->gtCallObjp;
+ GenTree* flagOp = call->gtCallArgs->gtOp.gtOp1;
+ GenTree* optTree = gtOptimizeEnumHasFlag(thisOp, flagOp);
+ if (optTree != nullptr)
+ {
+ return fgMorphTree(optTree);
+ }
+ }
+ }
+
genTreeOps simpleOp = GT_CALL;
if (methodID == CORINFO_INTRINSIC_TypeEQ)
{
diff --git a/src/jit/namedintrinsiclist.h b/src/jit/namedintrinsiclist.h
new file mode 100644
index 0000000000..cf81afc119
--- /dev/null
+++ b/src/jit/namedintrinsiclist.h
@@ -0,0 +1,16 @@
+// 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.
+
+#ifndef _NAMEDINTRINSICLIST_H_
+#define _NAMEDINTRINSICLIST_H_
+
+// Named jit intrinsics
+
+enum NamedIntrinsic
+{
+ NI_Illegal = 0,
+ NI_Enum_HasFlag = 1
+};
+
+#endif // _NAMEDINTRINSICLIST_H_
diff --git a/tests/src/JIT/opt/Enum/hasflag.cs b/tests/src/JIT/opt/Enum/hasflag.cs
new file mode 100644
index 0000000000..08b418b694
--- /dev/null
+++ b/tests/src/JIT/opt/Enum/hasflag.cs
@@ -0,0 +1,169 @@
+// 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.
+
+// Some simple tests for the Enum.HasFlag optimization.
+//
+// All the calls to HasFlag below are now optimized to simple compares,
+// except for the case in the try/catch.
+
+using System;
+
+enum E
+{
+ ZERO,
+ RED,
+ BLUE
+}
+
+enum F : ulong
+{
+ ZERO,
+ RED = 0x800000000UL,
+ BLUE = 0x1000000000UL
+}
+
+enum G : byte
+{
+ ZERO,
+ RED = 6,
+ BLUE = 9
+}
+
+struct EStruct
+{
+ public E e;
+}
+
+class EClass
+{
+ public E e;
+}
+
+class P
+{
+ static E[] ArrayOfE = { E.RED, E.BLUE };
+
+ static E StaticE = E.BLUE;
+
+ static E GetE()
+ {
+ return E.RED;
+ }
+
+ static E GetESideEffect()
+ {
+ E e = StaticE;
+ switch (e)
+ {
+ case E.RED:
+ StaticE = E.BLUE;
+ break;
+ case E.BLUE:
+ StaticE = E.RED;
+ break;
+ }
+ return e;
+ }
+
+ public static bool ByrefE(ref E e1, E e2)
+ {
+ return e1.HasFlag(e2);
+ }
+
+ public static bool ByrefF(ref F e1, F e2)
+ {
+ return e1.HasFlag(e2);
+ }
+
+ public static bool ByrefG(ref G e1, G e2)
+ {
+ return e1.HasFlag(e2);
+ }
+
+ public static int Main()
+ {
+ E e1 = E.RED;
+ E e2 = E.BLUE;
+
+ EClass ec = new EClass();
+ ec.e = E.RED;
+
+ EStruct es = new EStruct();
+ es.e = E.BLUE;
+
+ bool true0 = E.RED.HasFlag(E.RED);
+ bool true1 = e1.HasFlag(GetE());
+ bool true2 = ArrayOfE[0].HasFlag(E.RED);
+ bool true3 = StaticE.HasFlag(E.BLUE);
+ bool true4 = ec.e.HasFlag(e1);
+ bool true5 = e2.HasFlag(es.e);
+ bool true6 = e1.HasFlag(E.ZERO);
+ bool true7 = e2.HasFlag(E.ZERO);
+
+ bool false0 = E.BLUE.HasFlag(E.RED);
+ bool false1 = e1.HasFlag(e2);
+ bool false2 = GetE().HasFlag(e2);
+ bool false3 = E.RED.HasFlag(ArrayOfE[1]);
+ bool false4 = E.BLUE.HasFlag(ec.e);
+ bool false5 = ByrefE(ref e1, StaticE);
+
+ bool true8 = StaticE.HasFlag(GetESideEffect());
+ bool false6 = GetESideEffect().HasFlag(StaticE);
+
+ bool false7 = F.RED.HasFlag(F.BLUE);
+ bool false8 = G.RED.HasFlag(G.BLUE);
+
+ F f1 = F.RED;
+ bool false9 = ByrefF(ref f1, F.BLUE);
+
+ G g1 = G.RED;
+ bool true9 = ByrefG(ref g1, G.RED);
+
+ bool[] trueResults = {true0, true1, true2, true3, true4, true5, true6, true7, true8, true9};
+ bool[] falseResults = {false0, false1, false2, false3, false4, false5, false6, false7, false8, false9};
+
+ bool resultOk = true;
+
+ int i = 0;
+ foreach (var b in trueResults)
+ {
+ i++;
+ if (!b)
+ {
+ Console.WriteLine("true{0} failed\n", i);
+ resultOk = false;
+ }
+ }
+
+ i = 0;
+ foreach (var b in falseResults)
+ {
+ i++;
+ if (b)
+ {
+ Console.WriteLine("false{0} failed\n", i);
+ resultOk = false;
+ }
+ }
+
+ // Optimization should bail on this case which causes an exception.
+ bool didThrow = false;
+ try
+ {
+ bool badFlag = E.RED.HasFlag((Enum) F.RED);
+ }
+ catch (ArgumentException)
+ {
+ didThrow = true;
+ }
+
+ if (!didThrow)
+ {
+ Console.WriteLine("exception case failed\n");
+ resultOk = false;
+ }
+
+ return resultOk ? 100 : 0;
+ }
+}
diff --git a/tests/src/JIT/opt/Enum/hasflag.csproj b/tests/src/JIT/opt/Enum/hasflag.csproj
new file mode 100644
index 0000000000..42e9f46eba
--- /dev/null
+++ b/tests/src/JIT/opt/Enum/hasflag.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>1</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="hasflag.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
diff --git a/tests/src/JIT/opt/Enum/shared.cs b/tests/src/JIT/opt/Enum/shared.cs
new file mode 100644
index 0000000000..f15361a05a
--- /dev/null
+++ b/tests/src/JIT/opt/Enum/shared.cs
@@ -0,0 +1,41 @@
+// 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.
+
+// Some simple tests for the Enum.HasFlag optimization.
+// Verify the optimization avoids firing for shared types.
+
+using System;
+using System.Runtime.CompilerServices;
+
+class MyG<T,U>
+{
+ public enum A
+ {
+ X = 1
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void foo()
+ {
+ var a = MyG<object,U>.A.X;
+ a.HasFlag(MyG<T,string>.A.X);
+ }
+}
+
+class My
+{
+ public static int Main()
+ {
+ int result = 0;
+ try
+ {
+ MyG<My,My>.foo();
+ }
+ catch(ArgumentException)
+ {
+ result = 100;
+ }
+ return result;
+ }
+}
diff --git a/tests/src/JIT/opt/Enum/shared.csproj b/tests/src/JIT/opt/Enum/shared.csproj
new file mode 100644
index 0000000000..343593f21f
--- /dev/null
+++ b/tests/src/JIT/opt/Enum/shared.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>1</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="shared.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