diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 91c0e0cbc93ed5..f4ca318986c654 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4774,8 +4774,10 @@ class Compiler bool impIsCastHelperEligibleForClassProbe(GenTree* tree); bool impIsCastHelperMayHaveProfileData(CorInfoHelpFunc helper); + bool impMatchIsInstBooleanConversion(const BYTE* codeAddr, const BYTE* codeEndp, int* consumed); + GenTree* impCastClassOrIsInstToTree( - GenTree* op1, GenTree* op2, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass, IL_OFFSET ilOffset); + GenTree* op1, GenTree* op2, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass, bool* booleanCheck, IL_OFFSET ilOffset); GenTree* impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass); diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index a98a564d403b92..91b28742dc0af3 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -5464,6 +5464,53 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T return nullptr; } +//------------------------------------------------------------------------ +// impMatchIsInstBooleanConversion: Match IL to determine whether an isinst IL +// instruction is used for a simple boolean check. +// +// Arguments: +// codeAddr - IL after the isinst +// codeEndp - End of IL code stream +// consumed - [out] If this function returns true, set to the number of IL +// bytes to consume to create the boolean check +// +// Return Value: +// True if the isinst is used as a boolean check; otherwise false. +// +// Remarks: +// The isinst instruction is specced to return the original object refernce +// when the type check succeeds. However, in many cases it is used strictly +// as a boolean type check (if (x is Foo) for example). In those cases it is +// beneficial for the JIT if we avoid creating QMARKs returning the object +// itself which may disable some important optimization in some cases. +// +bool Compiler::impMatchIsInstBooleanConversion(const BYTE* codeAddr, const BYTE* codeEndp, int* consumed) +{ + OPCODE nextOpcode = impGetNonPrefixOpcode(codeAddr, codeEndp); + switch (nextOpcode) + { + case CEE_BRFALSE: + case CEE_BRFALSE_S: + case CEE_BRTRUE: + case CEE_BRTRUE_S: + // BRFALSE/BRTRUE importation are expected to transparently handle + // that the created tree is a TYP_INT instead of TYP_REF, so we do + // not consume them here. + *consumed = 0; + return true; + case CEE_LDNULL: + nextOpcode = impGetNonPrefixOpcode(codeAddr + 1, codeEndp); + if (nextOpcode == CEE_CGT_UN) + { + *consumed = 3; + return true; + } + return false; + default: + return false; + } +} + //------------------------------------------------------------------------ // impCastClassOrIsInstToTree: build and import castclass/isinst // @@ -5472,6 +5519,9 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T // op2 - type handle for type to cast to // pResolvedToken - resolved token from the cast operation // isCastClass - true if this is castclass, false means isinst +// booleanCheck - [in, out] If true, allow creating a boolean-returning check +// instead of returning the object reference. Set to false if this function +// was not able to create a boolean check. // // Return Value: // Tree representing the cast @@ -5479,8 +5529,12 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T // Notes: // May expand into a series of runtime checks or a helper call. // -GenTree* Compiler::impCastClassOrIsInstToTree( - GenTree* op1, GenTree* op2, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass, IL_OFFSET ilOffset) +GenTree* Compiler::impCastClassOrIsInstToTree(GenTree* op1, + GenTree* op2, + CORINFO_RESOLVED_TOKEN* pResolvedToken, + bool isCastClass, + bool* booleanCheck, + IL_OFFSET ilOffset) { assert(op1->TypeGet() == TYP_REF); @@ -5553,6 +5607,8 @@ GenTree* Compiler::impCastClassOrIsInstToTree( call->gtCallMoreFlags |= GTF_CALL_M_CAST_CAN_BE_EXPANDED; call->gtCastHelperILOffset = ilOffset; } + + *booleanCheck = false; return call; } @@ -5563,9 +5619,9 @@ GenTree* Compiler::impCastClassOrIsInstToTree( // Now we import it as two QMark nodes representing this: // // tmp = op1; - // if (tmp != null) // qmarkNull + // if (tmp != null) // condNull // { - // if (tmp->pMT == op2) // qmarkMT + // if (tmp->pMT == op2) // condMT // result = tmp; // else // result = null; @@ -5573,24 +5629,42 @@ GenTree* Compiler::impCastClassOrIsInstToTree( // else // result = null; // + // When a boolean check is possible we create 1/0 instead of tmp/null. // Spill op1 if it's a complex expression GenTree* op1Clone; op1 = impCloneExpr(op1, &op1Clone, CHECK_SPILL_ALL, nullptr DEBUGARG("ISINST eval op1")); - GenTreeOp* condMT = gtNewOperNode(GT_NE, TYP_INT, gtNewMethodTableLookup(op1Clone), op2); - GenTreeOp* condNull = gtNewOperNode(GT_EQ, TYP_INT, gtClone(op1), gtNewNull()); - GenTreeQmark* qmarkMT = gtNewQmarkNode(TYP_REF, condMT, gtNewColonNode(TYP_REF, gtNewNull(), gtClone(op1))); - GenTreeQmark* qmarkNull = gtNewQmarkNode(TYP_REF, condNull, gtNewColonNode(TYP_REF, gtNewNull(), qmarkMT)); + GenTreeOp* condNull = gtNewOperNode(GT_EQ, TYP_INT, gtClone(op1), gtNewNull()); + GenTreeOp* condMT = gtNewOperNode(GT_NE, TYP_INT, gtNewMethodTableLookup(op1Clone), op2); + + GenTreeQmark* qmarkResult; + + if (*booleanCheck) + { + GenTreeQmark* qmarkMT = + gtNewQmarkNode(TYP_INT, condMT, + gtNewColonNode(TYP_INT, gtNewZeroConNode(TYP_INT), gtNewOneConNode(TYP_INT))); + qmarkResult = gtNewQmarkNode(TYP_INT, condNull, gtNewColonNode(TYP_INT, gtNewZeroConNode(TYP_INT), qmarkMT)); + } + else + { + GenTreeQmark* qmarkMT = gtNewQmarkNode(TYP_REF, condMT, gtNewColonNode(TYP_REF, gtNewNull(), gtClone(op1))); + qmarkResult = gtNewQmarkNode(TYP_REF, condNull, gtNewColonNode(TYP_REF, gtNewNull(), qmarkMT)); + } // Make QMark node a top level node by spilling it. const unsigned result = lvaGrabTemp(true DEBUGARG("spilling qmarkNull")); - impStoreToTemp(result, qmarkNull, CHECK_SPILL_NONE); + impStoreToTemp(result, qmarkResult, CHECK_SPILL_NONE); - // See also gtGetHelperCallClassHandle where we make the same - // determination for the helper call variants. - lvaSetClass(result, pResolvedToken->hClass); - return gtNewLclvNode(result, TYP_REF); + if (!*booleanCheck) + { + // See also gtGetHelperCallClassHandle where we make the same + // determination for the helper call variants. + lvaSetClass(result, pResolvedToken->hClass); + } + + return gtNewLclvNode(result, qmarkResult->TypeGet()); } #ifndef DEBUG @@ -9626,7 +9700,14 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (!usingReadyToRunHelper) #endif { - op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, false, opcodeOffs); + int consumed = 0; + bool booleanCheck = impMatchIsInstBooleanConversion(codeAddr + sz, codeEndp, &consumed); + op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, false, &booleanCheck, opcodeOffs); + + if (booleanCheck) + { + sz += consumed; + } } if (compDonotInline()) { @@ -10148,7 +10229,8 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (!usingReadyToRunHelper) #endif { - op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, true, opcodeOffs); + bool booleanCheck = false; + op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, true, &booleanCheck, opcodeOffs); } if (compDonotInline()) {