Skip to content

Commit b2ccd98

Browse files
authored
JIT: Produce special cased IR for boolean isinst checks (#103391)
Currently the IR for boolean `isinst` checks ends up being something like `(x != null ? x.mt == expectedMT ? x : null : null) != null`, which the JIT ends up having a hard time clean up early. With object stack allocation this pattern usually leads to unnecessary address exposure. This adds a simple pattern match during import to produce different IR in the common cases where the `isinst` is just used as a boolean check. We instead produce IR like `(x != null ? x.mt == expectedMT ? 1 : 0 : 0) != 0`, which the JIT has an easier time with.
1 parent cb19811 commit b2ccd98

File tree

2 files changed

+100
-16
lines changed

2 files changed

+100
-16
lines changed

src/coreclr/jit/compiler.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -4777,8 +4777,10 @@ class Compiler
47774777
bool impIsCastHelperEligibleForClassProbe(GenTree* tree);
47784778
bool impIsCastHelperMayHaveProfileData(CorInfoHelpFunc helper);
47794779

4780+
bool impMatchIsInstBooleanConversion(const BYTE* codeAddr, const BYTE* codeEndp, int* consumed);
4781+
47804782
GenTree* impCastClassOrIsInstToTree(
4781-
GenTree* op1, GenTree* op2, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass, IL_OFFSET ilOffset);
4783+
GenTree* op1, GenTree* op2, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass, bool* booleanCheck, IL_OFFSET ilOffset);
47824784

47834785
GenTree* impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass);
47844786

src/coreclr/jit/importer.cpp

+97-15
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)