@@ -5468,6 +5468,53 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T
54685468 return nullptr;
54695469}
54705470
5471+ //------------------------------------------------------------------------
5472+ // impMatchIsInstBooleanConversion: Match IL to determine whether an isinst IL
5473+ // instruction is used for a simple boolean check.
5474+ //
5475+ // Arguments:
5476+ // codeAddr - IL after the isinst
5477+ // codeEndp - End of IL code stream
5478+ // consumed - [out] If this function returns true, set to the number of IL
5479+ // bytes to consume to create the boolean check
5480+ //
5481+ // Return Value:
5482+ // True if the isinst is used as a boolean check; otherwise false.
5483+ //
5484+ // Remarks:
5485+ // The isinst instruction is specced to return the original object refernce
5486+ // when the type check succeeds. However, in many cases it is used strictly
5487+ // as a boolean type check (if (x is Foo) for example). In those cases it is
5488+ // beneficial for the JIT if we avoid creating QMARKs returning the object
5489+ // itself which may disable some important optimization in some cases.
5490+ //
5491+ bool Compiler::impMatchIsInstBooleanConversion(const BYTE* codeAddr, const BYTE* codeEndp, int* consumed)
5492+ {
5493+ OPCODE nextOpcode = impGetNonPrefixOpcode(codeAddr, codeEndp);
5494+ switch (nextOpcode)
5495+ {
5496+ case CEE_BRFALSE:
5497+ case CEE_BRFALSE_S:
5498+ case CEE_BRTRUE:
5499+ case CEE_BRTRUE_S:
5500+ // BRFALSE/BRTRUE importation are expected to transparently handle
5501+ // that the created tree is a TYP_INT instead of TYP_REF, so we do
5502+ // not consume them here.
5503+ *consumed = 0;
5504+ return true;
5505+ case CEE_LDNULL:
5506+ nextOpcode = impGetNonPrefixOpcode(codeAddr + 1, codeEndp);
5507+ if (nextOpcode == CEE_CGT_UN)
5508+ {
5509+ *consumed = 3;
5510+ return true;
5511+ }
5512+ return false;
5513+ default:
5514+ return false;
5515+ }
5516+ }
5517+
54715518//------------------------------------------------------------------------
54725519// impCastClassOrIsInstToTree: build and import castclass/isinst
54735520//
@@ -5476,15 +5523,22 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T
54765523// op2 - type handle for type to cast to
54775524// pResolvedToken - resolved token from the cast operation
54785525// isCastClass - true if this is castclass, false means isinst
5526+ // booleanCheck - [in, out] If true, allow creating a boolean-returning check
5527+ // instead of returning the object reference. Set to false if this function
5528+ // was not able to create a boolean check.
54795529//
54805530// Return Value:
54815531// Tree representing the cast
54825532//
54835533// Notes:
54845534// May expand into a series of runtime checks or a helper call.
54855535//
5486- GenTree* Compiler::impCastClassOrIsInstToTree(
5487- GenTree* op1, GenTree* op2, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass, IL_OFFSET ilOffset)
5536+ GenTree* Compiler::impCastClassOrIsInstToTree(GenTree* op1,
5537+ GenTree* op2,
5538+ CORINFO_RESOLVED_TOKEN* pResolvedToken,
5539+ bool isCastClass,
5540+ bool* booleanCheck,
5541+ IL_OFFSET ilOffset)
54885542{
54895543 assert(op1->TypeGet() == TYP_REF);
54905544
@@ -5557,6 +5611,8 @@ GenTree* Compiler::impCastClassOrIsInstToTree(
55575611 call->gtCallMoreFlags |= GTF_CALL_M_CAST_CAN_BE_EXPANDED;
55585612 call->gtCastHelperILOffset = ilOffset;
55595613 }
5614+
5615+ *booleanCheck = false;
55605616 return call;
55615617 }
55625618
@@ -5567,34 +5623,52 @@ GenTree* Compiler::impCastClassOrIsInstToTree(
55675623 // Now we import it as two QMark nodes representing this:
55685624 //
55695625 // tmp = op1;
5570- // if (tmp != null) // qmarkNull
5626+ // if (tmp != null) // condNull
55715627 // {
5572- // if (tmp->pMT == op2) // qmarkMT
5628+ // if (tmp->pMT == op2) // condMT
55735629 // result = tmp;
55745630 // else
55755631 // result = null;
55765632 // }
55775633 // else
55785634 // result = null;
55795635 //
5636+ // When a boolean check is possible we create 1/0 instead of tmp/null.
55805637
55815638 // Spill op1 if it's a complex expression
55825639 GenTree* op1Clone;
55835640 op1 = impCloneExpr(op1, &op1Clone, CHECK_SPILL_ALL, nullptr DEBUGARG("ISINST eval op1"));
55845641
5585- GenTreeOp* condMT = gtNewOperNode(GT_NE, TYP_INT, gtNewMethodTableLookup(op1Clone), op2);
5586- GenTreeOp* condNull = gtNewOperNode(GT_EQ, TYP_INT, gtClone(op1), gtNewNull());
5587- GenTreeQmark* qmarkMT = gtNewQmarkNode(TYP_REF, condMT, gtNewColonNode(TYP_REF, gtNewNull(), gtClone(op1)));
5588- GenTreeQmark* qmarkNull = gtNewQmarkNode(TYP_REF, condNull, gtNewColonNode(TYP_REF, gtNewNull(), qmarkMT));
5642+ GenTreeOp* condNull = gtNewOperNode(GT_EQ, TYP_INT, gtClone(op1), gtNewNull());
5643+ GenTreeOp* condMT = gtNewOperNode(GT_NE, TYP_INT, gtNewMethodTableLookup(op1Clone), op2);
5644+
5645+ GenTreeQmark* qmarkResult;
5646+
5647+ if (*booleanCheck)
5648+ {
5649+ GenTreeQmark* qmarkMT =
5650+ gtNewQmarkNode(TYP_INT, condMT,
5651+ gtNewColonNode(TYP_INT, gtNewZeroConNode(TYP_INT), gtNewOneConNode(TYP_INT)));
5652+ qmarkResult = gtNewQmarkNode(TYP_INT, condNull, gtNewColonNode(TYP_INT, gtNewZeroConNode(TYP_INT), qmarkMT));
5653+ }
5654+ else
5655+ {
5656+ GenTreeQmark* qmarkMT = gtNewQmarkNode(TYP_REF, condMT, gtNewColonNode(TYP_REF, gtNewNull(), gtClone(op1)));
5657+ qmarkResult = gtNewQmarkNode(TYP_REF, condNull, gtNewColonNode(TYP_REF, gtNewNull(), qmarkMT));
5658+ }
55895659
55905660 // Make QMark node a top level node by spilling it.
55915661 const unsigned result = lvaGrabTemp(true DEBUGARG("spilling qmarkNull"));
5592- impStoreToTemp(result, qmarkNull , CHECK_SPILL_NONE);
5662+ impStoreToTemp(result, qmarkResult , CHECK_SPILL_NONE);
55935663
5594- // See also gtGetHelperCallClassHandle where we make the same
5595- // determination for the helper call variants.
5596- lvaSetClass(result, pResolvedToken->hClass);
5597- return gtNewLclvNode(result, TYP_REF);
5664+ if (!*booleanCheck)
5665+ {
5666+ // See also gtGetHelperCallClassHandle where we make the same
5667+ // determination for the helper call variants.
5668+ lvaSetClass(result, pResolvedToken->hClass);
5669+ }
5670+
5671+ return gtNewLclvNode(result, qmarkResult->TypeGet());
55985672}
55995673
56005674#ifndef DEBUG
@@ -9630,7 +9704,14 @@ void Compiler::impImportBlockCode(BasicBlock* block)
96309704 if (!usingReadyToRunHelper)
96319705#endif
96329706 {
9633- op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, false, opcodeOffs);
9707+ int consumed = 0;
9708+ bool booleanCheck = impMatchIsInstBooleanConversion(codeAddr + sz, codeEndp, &consumed);
9709+ op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, false, &booleanCheck, opcodeOffs);
9710+
9711+ if (booleanCheck)
9712+ {
9713+ sz += consumed;
9714+ }
96349715 }
96359716 if (compDonotInline())
96369717 {
@@ -10152,7 +10233,8 @@ void Compiler::impImportBlockCode(BasicBlock* block)
1015210233 if (!usingReadyToRunHelper)
1015310234#endif
1015410235 {
10155- op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, true, opcodeOffs);
10236+ bool booleanCheck = false;
10237+ op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, true, &booleanCheck, opcodeOffs);
1015610238 }
1015710239 if (compDonotInline())
1015810240 {
0 commit comments