@@ -5468,6 +5468,53 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T
5468
5468
return nullptr;
5469
5469
}
5470
5470
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
+
5471
5518
//------------------------------------------------------------------------
5472
5519
// impCastClassOrIsInstToTree: build and import castclass/isinst
5473
5520
//
@@ -5476,15 +5523,22 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T
5476
5523
// op2 - type handle for type to cast to
5477
5524
// pResolvedToken - resolved token from the cast operation
5478
5525
// 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.
5479
5529
//
5480
5530
// Return Value:
5481
5531
// Tree representing the cast
5482
5532
//
5483
5533
// Notes:
5484
5534
// May expand into a series of runtime checks or a helper call.
5485
5535
//
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)
5488
5542
{
5489
5543
assert(op1->TypeGet() == TYP_REF);
5490
5544
@@ -5557,6 +5611,8 @@ GenTree* Compiler::impCastClassOrIsInstToTree(
5557
5611
call->gtCallMoreFlags |= GTF_CALL_M_CAST_CAN_BE_EXPANDED;
5558
5612
call->gtCastHelperILOffset = ilOffset;
5559
5613
}
5614
+
5615
+ *booleanCheck = false;
5560
5616
return call;
5561
5617
}
5562
5618
@@ -5567,34 +5623,52 @@ GenTree* Compiler::impCastClassOrIsInstToTree(
5567
5623
// Now we import it as two QMark nodes representing this:
5568
5624
//
5569
5625
// tmp = op1;
5570
- // if (tmp != null) // qmarkNull
5626
+ // if (tmp != null) // condNull
5571
5627
// {
5572
- // if (tmp->pMT == op2) // qmarkMT
5628
+ // if (tmp->pMT == op2) // condMT
5573
5629
// result = tmp;
5574
5630
// else
5575
5631
// result = null;
5576
5632
// }
5577
5633
// else
5578
5634
// result = null;
5579
5635
//
5636
+ // When a boolean check is possible we create 1/0 instead of tmp/null.
5580
5637
5581
5638
// Spill op1 if it's a complex expression
5582
5639
GenTree* op1Clone;
5583
5640
op1 = impCloneExpr(op1, &op1Clone, CHECK_SPILL_ALL, nullptr DEBUGARG("ISINST eval op1"));
5584
5641
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
+ }
5589
5659
5590
5660
// Make QMark node a top level node by spilling it.
5591
5661
const unsigned result = lvaGrabTemp(true DEBUGARG("spilling qmarkNull"));
5592
- impStoreToTemp(result, qmarkNull , CHECK_SPILL_NONE);
5662
+ impStoreToTemp(result, qmarkResult , CHECK_SPILL_NONE);
5593
5663
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());
5598
5672
}
5599
5673
5600
5674
#ifndef DEBUG
@@ -9630,7 +9704,14 @@ void Compiler::impImportBlockCode(BasicBlock* block)
9630
9704
if (!usingReadyToRunHelper)
9631
9705
#endif
9632
9706
{
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
+ }
9634
9715
}
9635
9716
if (compDonotInline())
9636
9717
{
@@ -10152,7 +10233,8 @@ void Compiler::impImportBlockCode(BasicBlock* block)
10152
10233
if (!usingReadyToRunHelper)
10153
10234
#endif
10154
10235
{
10155
- op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, true, opcodeOffs);
10236
+ bool booleanCheck = false;
10237
+ op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, true, &booleanCheck, opcodeOffs);
10156
10238
}
10157
10239
if (compDonotInline())
10158
10240
{
0 commit comments