From da8589f4e92f443b72581e5cf11ec170a7e01813 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 10 Jun 2023 23:51:35 +0800 Subject: [PATCH 1/5] Initial attempt --- src/coreclr/jit/compiler.h | 2 + src/coreclr/jit/importer.cpp | 119 +++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 51c7109aa1dd31..efbfa16b1150d0 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4346,6 +4346,8 @@ class Compiler GenTree* impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass); + int impIsInstPatternMatch(GenTree* op1, GenTree* op2, const BYTE* codeAddr, const BYTE* codeEndp); + bool VarTypeIsMultiByteAndCanEnreg(var_types type, CORINFO_CLASS_HANDLE typeClass, unsigned* typeSize, diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 722afadcd9a210..1b0da16589a33e 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -5310,6 +5310,112 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T return nullptr; } +//------------------------------------------------------------------------ +// impIsInstPatternMatch: match and import common isinst idioms +// +// Arguments: +// op1 - value to cast +// op2 - type handle for type to cast to +// codeAddr - position in IL stream after the box instruction +// codeEndp - end of IL stream +// +// Return Value: +// Number of IL bytes matched and imported, -1 otherwise +// +// Notes: +// op2 is known to represent an exact type (sealed or value type). + +int Compiler::impIsInstPatternMatch( + GenTree* op1, GenTree* op2, const BYTE* codeAddr, const BYTE* codeEndp) +{ + if ((impGetNonPrefixOpcode(codeAddr, codeEndp) == CEE_LDNULL)) + { + switch (impGetNonPrefixOpcode(codeAddr + 1, codeEndp)) + { + case CEE_CGT_UN: + // isinst + ldnull + cgt.un + // convert to op1 == null ? false : *op1 == op2 + { + GenTree* condNull; + // + // expand the null check: + // + // condNull ==> GT_NE + // / \. + // op1Copy CNS_INT + // null + // + condNull = gtNewOperNode(GT_NE, TYP_INT, gtClone(op1), gtNewNull()); + + GenTree* temp; + GenTree* condMT; + // + // expand the methodtable match: + // + // condMT ==> GT_EQ + // / \. + // GT_IND op2 (typically CNS_INT) + // | + // op1Copy + // + + // This can replace op1 with a GT_COMMA that evaluates op1 into a local + // + op1 = impCloneExpr(op1, &temp, CHECK_SPILL_ALL, nullptr DEBUGARG("CASTCLASS eval op1")); + // + // op1 is now known to be a non-complex tree + // thus we can use gtClone(op1) from now on + // + + GenTree* op2Var = op2; + temp = gtNewMethodTableLookup(temp); + condMT = gtNewOperNode(GT_EQ, TYP_INT, temp, op2); + + GenTree* qmarkNull; + // + // Generate QMARK - COLON tree + // + // qmarkNull ==> GT_QMARK + // / \. + // condNull GT_COLON + // / \. + // condMT CNS_INT + // false + // + temp = new (this, GT_COLON) GenTreeColon(TYP_INT, condMT, gtNewFalse()); + qmarkNull = gtNewQmarkNode(TYP_INT, condNull, temp->AsColon()); + + // Make QMark node a top level node by spilling it. + unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling QMark")); + impStoreTemp(tmp, qmarkNull, CHECK_SPILL_NONE); + + GenTree* lclVar = gtNewLclvNode(tmp, TYP_INT); + impPushOnStack(lclVar, TYP_INT); + return 3; + } + + case CEE_CEQ: + // isinst + ldnull + ceq + // convert to op1 == null ? true : *op1 != op2 + + break; + + case CEE_BRTRUE: + case CEE_BRTRUE_S: + break; + + case CEE_BRFALSE: + case CEE_BRFALSE_S: + + break; + default: + return -1; + } + } + + return -1; +} + //------------------------------------------------------------------------ // impCastClassOrIsInstToTree: build and import castclass/isinst // @@ -9587,6 +9693,19 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (!usingReadyToRunHelper) #endif { + if (impIsClassExact(resolvedToken.hClass)) + { + // try to fold null check after exact isinst to MT comparison + int matched = impIsInstPatternMatch(op1, op2, codeAddr + sz, codeEndp); + + if (matched >= 0) + { + // Skip the matched IL instructions + sz += matched; + break; + } + } + op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, false, opcodeOffs); } if (compDonotInline()) From 48bb261ee8c4d010a0b65e104dba4b2a0d80394b Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 11 Jun 2023 00:30:41 +0800 Subject: [PATCH 2/5] Also for brtrue --- src/coreclr/jit/importer.cpp | 205 +++++++++++++++++++++++------------ 1 file changed, 134 insertions(+), 71 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 1b0da16589a33e..8edbf3e5d48ca3 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -5328,89 +5328,152 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T int Compiler::impIsInstPatternMatch( GenTree* op1, GenTree* op2, const BYTE* codeAddr, const BYTE* codeEndp) { - if ((impGetNonPrefixOpcode(codeAddr, codeEndp) == CEE_LDNULL)) + switch (impGetNonPrefixOpcode(codeAddr, codeEndp)) { - switch (impGetNonPrefixOpcode(codeAddr + 1, codeEndp)) - { - case CEE_CGT_UN: - // isinst + ldnull + cgt.un - // convert to op1 == null ? false : *op1 == op2 - { - GenTree* condNull; - // - // expand the null check: - // - // condNull ==> GT_NE - // / \. - // op1Copy CNS_INT - // null - // - condNull = gtNewOperNode(GT_NE, TYP_INT, gtClone(op1), gtNewNull()); + case CEE_LDNULL: + switch (impGetNonPrefixOpcode(codeAddr + 1, codeEndp)) + { + case CEE_CGT_UN: + // isinst + ldnull + cgt.un + // convert to op1 == null ? false : *op1 == op2 + { + GenTree* condNull; + // + // expand the null check: + // + // condNull ==> GT_NE + // / \. + // op1Copy CNS_INT + // null + // + condNull = gtNewOperNode(GT_NE, TYP_INT, gtClone(op1), gtNewNull()); - GenTree* temp; - GenTree* condMT; - // - // expand the methodtable match: - // - // condMT ==> GT_EQ - // / \. - // GT_IND op2 (typically CNS_INT) - // | - // op1Copy - // + GenTree* temp; + GenTree* condMT; + // + // expand the methodtable match: + // + // condMT ==> GT_EQ + // / \. + // GT_IND op2 (typically CNS_INT) + // | + // op1Copy + // - // This can replace op1 with a GT_COMMA that evaluates op1 into a local - // - op1 = impCloneExpr(op1, &temp, CHECK_SPILL_ALL, nullptr DEBUGARG("CASTCLASS eval op1")); - // - // op1 is now known to be a non-complex tree - // thus we can use gtClone(op1) from now on - // + // This can replace op1 with a GT_COMMA that evaluates op1 into a local + // + op1 = impCloneExpr(op1, &temp, CHECK_SPILL_ALL, nullptr DEBUGARG("CASTCLASS eval op1")); + // + // op1 is now known to be a non-complex tree + // thus we can use gtClone(op1) from now on + // - GenTree* op2Var = op2; - temp = gtNewMethodTableLookup(temp); - condMT = gtNewOperNode(GT_EQ, TYP_INT, temp, op2); + GenTree* op2Var = op2; + temp = gtNewMethodTableLookup(temp); + condMT = gtNewOperNode(GT_EQ, TYP_INT, temp, op2); - GenTree* qmarkNull; - // - // Generate QMARK - COLON tree - // - // qmarkNull ==> GT_QMARK - // / \. - // condNull GT_COLON - // / \. - // condMT CNS_INT - // false - // - temp = new (this, GT_COLON) GenTreeColon(TYP_INT, condMT, gtNewFalse()); - qmarkNull = gtNewQmarkNode(TYP_INT, condNull, temp->AsColon()); + GenTree* qmarkNull; + // + // Generate QMARK - COLON tree + // + // qmarkNull ==> GT_QMARK + // / \. + // condNull GT_COLON + // / \. + // condMT CNS_INT + // false + // + temp = new (this, GT_COLON) GenTreeColon(TYP_INT, condMT, gtNewFalse()); + qmarkNull = gtNewQmarkNode(TYP_INT, condNull, temp->AsColon()); - // Make QMark node a top level node by spilling it. - unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling QMark")); - impStoreTemp(tmp, qmarkNull, CHECK_SPILL_NONE); + // Make QMark node a top level node by spilling it. + unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling QMark")); + impStoreTemp(tmp, qmarkNull, CHECK_SPILL_NONE); - GenTree* lclVar = gtNewLclvNode(tmp, TYP_INT); - impPushOnStack(lclVar, TYP_INT); - return 3; - } + GenTree* lclVar = gtNewLclvNode(tmp, TYP_INT); + impPushOnStack(lclVar, TYP_INT); + return 3; + } - case CEE_CEQ: - // isinst + ldnull + ceq - // convert to op1 == null ? true : *op1 != op2 + case CEE_CEQ: + // isinst + ldnull + ceq + // convert to op1 == null ? true : *op1 != op2 - break; + break; + default: + return -1; + } + break; - case CEE_BRTRUE: - case CEE_BRTRUE_S: - break; + case CEE_BRTRUE: + case CEE_BRTRUE_S: + // isinst + brtrue + // convert isinst to op1 == null ? false : *op1 == op2, leave the branch IL + { + GenTree* condNull; + // + // expand the null check: + // + // condNull ==> GT_NE + // / \. + // op1Copy CNS_INT + // null + // + condNull = gtNewOperNode(GT_NE, TYP_INT, gtClone(op1), gtNewNull()); - case CEE_BRFALSE: - case CEE_BRFALSE_S: + GenTree* temp; + GenTree* condMT; + // + // expand the methodtable match: + // + // condMT ==> GT_EQ + // / \. + // GT_IND op2 (typically CNS_INT) + // | + // op1Copy + // - break; - default: - return -1; - } + // This can replace op1 with a GT_COMMA that evaluates op1 into a local + // + op1 = impCloneExpr(op1, &temp, CHECK_SPILL_ALL, nullptr DEBUGARG("CASTCLASS eval op1")); + // + // op1 is now known to be a non-complex tree + // thus we can use gtClone(op1) from now on + // + + GenTree* op2Var = op2; + temp = gtNewMethodTableLookup(temp); + condMT = gtNewOperNode(GT_EQ, TYP_INT, temp, op2); + + GenTree* qmarkNull; + // + // Generate QMARK - COLON tree + // + // qmarkNull ==> GT_QMARK + // / \. + // condNull GT_COLON + // / \. + // condMT CNS_INT + // false + // + temp = new (this, GT_COLON) GenTreeColon(TYP_INT, condMT, gtNewFalse()); + qmarkNull = gtNewQmarkNode(TYP_INT, condNull, temp->AsColon()); + + // Make QMark node a top level node by spilling it. + unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling QMark")); + impStoreTemp(tmp, qmarkNull, CHECK_SPILL_NONE); + + GenTree* lclVar = gtNewLclvNode(tmp, TYP_INT); + impPushOnStack(lclVar, TYP_INT); + return 0; + } + + case CEE_BRFALSE: + case CEE_BRFALSE_S: + + break; + default: + return -1; } return -1; From 42e55b4860509d7111769b4ecbbd99a31b1ff3cb Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 11 Jun 2023 01:08:54 +0800 Subject: [PATCH 3/5] Cover reverse cases --- src/coreclr/jit/importer.cpp | 198 ++++++++++++++--------------------- 1 file changed, 77 insertions(+), 121 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 8edbf3e5d48ca3..9cd68a567d62ff 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -5328,6 +5328,9 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T int Compiler::impIsInstPatternMatch( GenTree* op1, GenTree* op2, const BYTE* codeAddr, const BYTE* codeEndp) { + int imported = -1; + bool isNotNullTest = false; + switch (impGetNonPrefixOpcode(codeAddr, codeEndp)) { case CEE_LDNULL: @@ -5335,71 +5338,16 @@ int Compiler::impIsInstPatternMatch( { case CEE_CGT_UN: // isinst + ldnull + cgt.un - // convert to op1 == null ? false : *op1 == op2 - { - GenTree* condNull; - // - // expand the null check: - // - // condNull ==> GT_NE - // / \. - // op1Copy CNS_INT - // null - // - condNull = gtNewOperNode(GT_NE, TYP_INT, gtClone(op1), gtNewNull()); - - GenTree* temp; - GenTree* condMT; - // - // expand the methodtable match: - // - // condMT ==> GT_EQ - // / \. - // GT_IND op2 (typically CNS_INT) - // | - // op1Copy - // - - // This can replace op1 with a GT_COMMA that evaluates op1 into a local - // - op1 = impCloneExpr(op1, &temp, CHECK_SPILL_ALL, nullptr DEBUGARG("CASTCLASS eval op1")); - // - // op1 is now known to be a non-complex tree - // thus we can use gtClone(op1) from now on - // - - GenTree* op2Var = op2; - temp = gtNewMethodTableLookup(temp); - condMT = gtNewOperNode(GT_EQ, TYP_INT, temp, op2); - - GenTree* qmarkNull; - // - // Generate QMARK - COLON tree - // - // qmarkNull ==> GT_QMARK - // / \. - // condNull GT_COLON - // / \. - // condMT CNS_INT - // false - // - temp = new (this, GT_COLON) GenTreeColon(TYP_INT, condMT, gtNewFalse()); - qmarkNull = gtNewQmarkNode(TYP_INT, condNull, temp->AsColon()); - - // Make QMark node a top level node by spilling it. - unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling QMark")); - impStoreTemp(tmp, qmarkNull, CHECK_SPILL_NONE); - - GenTree* lclVar = gtNewLclvNode(tmp, TYP_INT); - impPushOnStack(lclVar, TYP_INT); - return 3; - } + imported = 3; + isNotNullTest = true; + break; case CEE_CEQ: // isinst + ldnull + ceq - // convert to op1 == null ? true : *op1 != op2 - + imported = 3; + isNotNullTest = false; break; + default: return -1; } @@ -5408,75 +5356,83 @@ int Compiler::impIsInstPatternMatch( case CEE_BRTRUE: case CEE_BRTRUE_S: // isinst + brtrue - // convert isinst to op1 == null ? false : *op1 == op2, leave the branch IL - { - GenTree* condNull; - // - // expand the null check: - // - // condNull ==> GT_NE - // / \. - // op1Copy CNS_INT - // null - // - condNull = gtNewOperNode(GT_NE, TYP_INT, gtClone(op1), gtNewNull()); + // leave the branch IL + imported = 0; + isNotNullTest = true; + break; - GenTree* temp; - GenTree* condMT; - // - // expand the methodtable match: - // - // condMT ==> GT_EQ - // / \. - // GT_IND op2 (typically CNS_INT) - // | - // op1Copy - // + case CEE_BRFALSE: + case CEE_BRFALSE_S: + // isinst + false + // leave the branch IL + imported = 0; + isNotNullTest = false; + break; - // This can replace op1 with a GT_COMMA that evaluates op1 into a local - // - op1 = impCloneExpr(op1, &temp, CHECK_SPILL_ALL, nullptr DEBUGARG("CASTCLASS eval op1")); - // - // op1 is now known to be a non-complex tree - // thus we can use gtClone(op1) from now on - // + default: + return -1; + } - GenTree* op2Var = op2; - temp = gtNewMethodTableLookup(temp); - condMT = gtNewOperNode(GT_EQ, TYP_INT, temp, op2); + // Converting isinst followed by null test to: + // non-null test: op1 != null ? *op1 == op2 : false + // null test: op1 != null ? *op1 != op2 : true - GenTree* qmarkNull; - // - // Generate QMARK - COLON tree - // - // qmarkNull ==> GT_QMARK - // / \. - // condNull GT_COLON - // / \. - // condMT CNS_INT - // false - // - temp = new (this, GT_COLON) GenTreeColon(TYP_INT, condMT, gtNewFalse()); - qmarkNull = gtNewQmarkNode(TYP_INT, condNull, temp->AsColon()); + GenTree* temp; + GenTree* condMT; + // + // expand the methodtable match: + // + // condMT ==> GT_EQ/NE + // / \. + // GT_IND op2 (typically CNS_INT) + // | + // op1Copy + // - // Make QMark node a top level node by spilling it. - unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling QMark")); - impStoreTemp(tmp, qmarkNull, CHECK_SPILL_NONE); + // This can replace op1 with a GT_COMMA that evaluates op1 into a local + // + op1 = impCloneExpr(op1, &temp, CHECK_SPILL_ALL, nullptr DEBUGARG("CASTCLASS eval op1")); + // + // op1 is now known to be a non-complex tree + // thus we can use gtClone(op1) from now on + // - GenTree* lclVar = gtNewLclvNode(tmp, TYP_INT); - impPushOnStack(lclVar, TYP_INT); - return 0; - } + GenTree* op2Var = op2; + temp = gtNewMethodTableLookup(temp); + condMT = gtNewOperNode(isNotNullTest ? GT_EQ : GT_NE, TYP_INT, temp, op2); - case CEE_BRFALSE: - case CEE_BRFALSE_S: + GenTree* condNull; + // + // expand the null check: + // + // condNull ==> GT_NE + // / \. + // op1Copy CNS_INT + // null + // + condNull = gtNewOperNode(GT_NE, TYP_INT, gtClone(op1), gtNewNull()); - break; - default: - return -1; - } + GenTree* qmarkNull; + // + // Generate QMARK - COLON tree + // + // qmarkNull ==> GT_QMARK + // / \. + // condNull GT_COLON + // / \. + // condMT CNS_INT + // false/true + // + temp = gtNewColonNode(TYP_INT, condMT, isNotNullTest ? gtNewFalse() : gtNewTrue()); + qmarkNull = gtNewQmarkNode(TYP_INT, condNull, temp->AsColon()); - return -1; + // Make QMark node a top level node by spilling it. + unsigned tmp = lvaGrabTemp(true DEBUGARG("spilling QMark")); + impStoreTemp(tmp, qmarkNull, CHECK_SPILL_NONE); + + GenTree* lclVar = gtNewLclvNode(tmp, TYP_INT); + impPushOnStack(lclVar, TYP_INT); + return imported; } //------------------------------------------------------------------------ From 23f46c0897f0c3af27c3d1e1f339a28562dc4d30 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 11 Jun 2023 01:14:59 +0800 Subject: [PATCH 4/5] Invert the null check condition --- src/coreclr/jit/importer.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 9cd68a567d62ff..488d506f85ce9c 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -5374,8 +5374,8 @@ int Compiler::impIsInstPatternMatch( } // Converting isinst followed by null test to: - // non-null test: op1 != null ? *op1 == op2 : false - // null test: op1 != null ? *op1 != op2 : true + // non-null test: op1 == null ? false : *op1 == op2 + // null test: op1 == null ? true : *op1 != op2 GenTree* temp; GenTree* condMT; @@ -5405,12 +5405,12 @@ int Compiler::impIsInstPatternMatch( // // expand the null check: // - // condNull ==> GT_NE + // condNull ==> GT_EQ // / \. // op1Copy CNS_INT // null // - condNull = gtNewOperNode(GT_NE, TYP_INT, gtClone(op1), gtNewNull()); + condNull = gtNewOperNode(GT_EQ, TYP_INT, gtClone(op1), gtNewNull()); GenTree* qmarkNull; // @@ -5420,10 +5420,10 @@ int Compiler::impIsInstPatternMatch( // / \. // condNull GT_COLON // / \. - // condMT CNS_INT - // false/true + // CNS_INT condMT + // false/true // - temp = gtNewColonNode(TYP_INT, condMT, isNotNullTest ? gtNewFalse() : gtNewTrue()); + temp = gtNewColonNode(TYP_INT, isNotNullTest ? gtNewFalse() : gtNewTrue(), condMT); qmarkNull = gtNewQmarkNode(TYP_INT, condNull, temp->AsColon()); // Make QMark node a top level node by spilling it. From 68b164de671dee2ff21dd0f0b705473ad3198132 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 23 Jun 2023 19:38:43 +0800 Subject: [PATCH 5/5] Share nullable transform --- src/coreclr/jit/compiler.h | 4 +- src/coreclr/jit/importer.cpp | 75 ++++++++++++++++++++++++------------ 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index efbfa16b1150d0..6420edaa09eb11 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4345,8 +4345,8 @@ class Compiler GenTree* op1, GenTree* op2, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass, IL_OFFSET ilOffset); GenTree* impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_TOKEN* pResolvedToken, bool isCastClass); - - int impIsInstPatternMatch(GenTree* op1, GenTree* op2, const BYTE* codeAddr, const BYTE* codeEndp); + bool impExtractNullableForCastClassOrIsInst(GenTree** op2, CORINFO_RESOLVED_TOKEN* pResolvedToken); + int impIsInstPatternMatch(GenTree* op1, GenTree* op2, CORINFO_RESOLVED_TOKEN* pResolvedToken, const BYTE* codeAddr, const BYTE* codeEndp); bool VarTypeIsMultiByteAndCanEnreg(var_types type, CORINFO_CLASS_HANDLE typeClass, diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 488d506f85ce9c..82fe969628564b 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -5310,6 +5310,42 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T return nullptr; } +//------------------------------------------------------------------------ +// impExtractNullableForCastClassOrIsInst: change nullable to underlying type for casting +// +// Arguments: +// pOp2 - type handle for type to cast to +// pResolvedToken - resolved token from the cast operation +// +// Return Value: +// If the cast can be expanded to comparison of method table to op2. +// +// Notes: +// If op2 is updated, resolvedToken will also be updated correspondingly. + +bool Compiler::impExtractNullableForCastClassOrIsInst(GenTree** pOp2, CORINFO_RESOLVED_TOKEN* pResolvedToken) +{ + // ECMA-335 III.4.3: If typeTok is a nullable type, Nullable, it is interpreted as "boxed" T + // We can convert constant-ish tokens of nullable to its underlying type. + // However, when the type is shared generic parameter like Nullable>, the actual type will require + // runtime lookup. It's too complex to add another level of indirection in op2, fallback to the cast helper instead. + + if (info.compCompHnd->getClassAttribs(pResolvedToken->hClass) & CORINFO_FLG_SHAREDINST) + return false; + + CORINFO_CLASS_HANDLE hClass = info.compCompHnd->getTypeForBox(pResolvedToken->hClass); + + if (hClass != pResolvedToken->hClass) + { + bool runtimeLookup; + pResolvedToken->hClass = hClass; + *pOp2 = impTokenToHandle(pResolvedToken, &runtimeLookup); + assert(!runtimeLookup); + } + + return true; +} + //------------------------------------------------------------------------ // impIsInstPatternMatch: match and import common isinst idioms // @@ -5326,11 +5362,14 @@ GenTree* Compiler::impOptimizeCastClassOrIsInst(GenTree* op1, CORINFO_RESOLVED_T // op2 is known to represent an exact type (sealed or value type). int Compiler::impIsInstPatternMatch( - GenTree* op1, GenTree* op2, const BYTE* codeAddr, const BYTE* codeEndp) + GenTree* op1, GenTree* op2, CORINFO_RESOLVED_TOKEN* pResolvedToken, const BYTE* codeAddr, const BYTE* codeEndp) { int imported = -1; bool isNotNullTest = false; + if (!impIsClassExact(pResolvedToken->hClass) || !impExtractNullableForCastClassOrIsInst(&op2, pResolvedToken)) + return -1; + switch (impGetNonPrefixOpcode(codeAddr, codeEndp)) { case CEE_LDNULL: @@ -5459,21 +5498,10 @@ GenTree* Compiler::impCastClassOrIsInstToTree( bool shouldExpandInline = true; bool isClassExact = impIsClassExact(pResolvedToken->hClass); - // ECMA-335 III.4.3: If typeTok is a nullable type, Nullable, it is interpreted as "boxed" T - // We can convert constant-ish tokens of nullable to its underlying type. - // However, when the type is shared generic parameter like Nullable>, the actual type will require - // runtime lookup. It's too complex to add another level of indirection in op2, fallback to the cast helper instead. - if (isClassExact && !(info.compCompHnd->getClassAttribs(pResolvedToken->hClass) & CORINFO_FLG_SHAREDINST)) + if (isClassExact) { - CORINFO_CLASS_HANDLE hClass = info.compCompHnd->getTypeForBox(pResolvedToken->hClass); - - if (hClass != pResolvedToken->hClass) - { - bool runtimeLookup; - pResolvedToken->hClass = hClass; - op2 = impTokenToHandle(pResolvedToken, &runtimeLookup); - assert(!runtimeLookup); - } + // Convert nullable to underlying type when appropriate. + impExtractNullableForCastClassOrIsInst(&op2, pResolvedToken); } // Profitability check. @@ -9712,17 +9740,14 @@ void Compiler::impImportBlockCode(BasicBlock* block) if (!usingReadyToRunHelper) #endif { - if (impIsClassExact(resolvedToken.hClass)) - { - // try to fold null check after exact isinst to MT comparison - int matched = impIsInstPatternMatch(op1, op2, codeAddr + sz, codeEndp); + // try to fold null check after exact isinst to MT comparison first + int matched = impIsInstPatternMatch(op1, op2, &resolvedToken, codeAddr + sz, codeEndp); - if (matched >= 0) - { - // Skip the matched IL instructions - sz += matched; - break; - } + if (matched >= 0) + { + // Skip the matched IL instructions + sz += matched; + break; } op1 = impCastClassOrIsInstToTree(op1, op2, &resolvedToken, false, opcodeOffs);