-
Notifications
You must be signed in to change notification settings - Fork 5.2k
JIT: Produce less convoluted IR for boolean isinst checks
#103391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 5 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
14ac08d
JIT: Produce less convoluted IR for boolean `isinst` checks
jakobbotsch 1ca8a25
Use two QMARKs like other case
jakobbotsch b313ec5
Run jit-format, add function headers
jakobbotsch 0438a0b
Oops
jakobbotsch c69f319
Feedback
jakobbotsch cb72437
Dedup code a bit
jakobbotsch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,15 +5519,22 @@ 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 | ||
| // | ||
| // 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; | ||
| } | ||
|
|
||
|
|
@@ -5578,19 +5634,37 @@ GenTree* Compiler::impCastClassOrIsInstToTree( | |
| 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)); | ||
| if (*booleanCheck) | ||
| { | ||
| 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_INT, gtNewZeroConNode(TYP_INT), gtNewOneConNode(TYP_INT))); | ||
| GenTreeQmark* qmarkNull = | ||
| gtNewQmarkNode(TYP_INT, condNull, gtNewColonNode(TYP_INT, gtNewZeroConNode(TYP_INT), 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); | ||
| // Make QMark node a top level node by spilling it. | ||
| const unsigned result = lvaGrabTemp(true DEBUGARG("spilling qmarkNull")); | ||
| impStoreToTemp(result, qmarkNull, CHECK_SPILL_NONE); | ||
| return gtNewLclvNode(result, TYP_INT); | ||
| } | ||
| else | ||
| { | ||
| 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)); | ||
|
|
||
| // Make QMark node a top level node by spilling it. | ||
| const unsigned result = lvaGrabTemp(true DEBUGARG("spilling qmarkNull")); | ||
| impStoreToTemp(result, qmarkNull, 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); | ||
| // See also gtGetHelperCallClassHandle where we make the same | ||
| // determination for the helper call variants. | ||
| lvaSetClass(result, pResolvedToken->hClass); | ||
| return gtNewLclvNode(result, TYP_REF); | ||
| } | ||
| } | ||
|
|
||
| #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()) | ||
| { | ||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.