diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index a066b23403609a..c8790c3f86e32c 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -2727,11 +2727,6 @@ void Compiler::optAssertionGen(GenTree* tree) assertionInfo = optCreateAssertion(tree->AsArrCommon()->ArrRef(), nullptr, OAK_NOT_EQUAL); break; - case GT_NULLCHECK: - // Explicit null checks always create non-null assertions. - assertionInfo = optCreateAssertion(tree->AsIndir()->Addr(), nullptr, OAK_NOT_EQUAL); - break; - case GT_INTRINSIC: if (tree->AsIntrinsic()->gtIntrinsicName == NI_System_Object_GetType) { @@ -5041,7 +5036,6 @@ GenTree* Compiler::optAssertionProp(ASSERT_VALARG_TP assertions, GenTree* tree, case GT_OBJ: case GT_BLK: case GT_IND: - case GT_NULLCHECK: case GT_STORE_DYN_BLK: return optAssertionProp_Ind(assertions, tree, stmt); diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 3502673faacac1..a9277f7bf4ade5 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -1282,6 +1282,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX void genCodeForLclAddr(GenTreeLclVarCommon* lclAddrNode); void genCodeForIndexAddr(GenTreeIndexAddr* tree); void genCodeForIndir(GenTreeIndir* tree); + void genEmitCodeForUnusedIndir(GenTreeIndir* tree); void genCodeForNegNot(GenTree* tree); void genCodeForBswap(GenTree* tree); bool genCanOmitNormalizationForBswap16(GenTree* tree); @@ -1299,7 +1300,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX void genCodeForCpBlkHelper(GenTreeBlk* cpBlkNode); #endif void genCodeForPhysReg(GenTreePhysReg* tree); - void genCodeForNullCheck(GenTreeIndir* tree); void genCodeForCmpXchg(GenTreeCmpXchg* tree); void genAlignStackBeforeCall(GenTreePutArgStk* putArgStk); diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index f7bb7a91f14e23..3dd4fadfa3d0f2 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -484,10 +484,6 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genCodeForPhysReg(treeNode->AsPhysReg()); break; - case GT_NULLCHECK: - genCodeForNullCheck(treeNode->AsIndir()); - break; - case GT_CATCH_ARG: noway_assert(handlerGetsXcptnObj(compiler->compCurBB->bbCatchTyp)); @@ -1453,30 +1449,6 @@ void CodeGen::genCodeForPhysReg(GenTreePhysReg* tree) genProduceReg(tree); } -//--------------------------------------------------------------------- -// genCodeForNullCheck - generate code for a GT_NULLCHECK node -// -// Arguments -// tree - the GT_NULLCHECK node -// -// Return value: -// None -// -void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) -{ -#ifdef TARGET_ARM - assert(!"GT_NULLCHECK isn't supported for Arm32; use GT_IND."); -#else - assert(tree->OperIs(GT_NULLCHECK)); - GenTree* op1 = tree->gtOp1; - - genConsumeRegs(op1); - regNumber targetReg = REG_ZR; - - GetEmitter()->emitInsLoadStoreOp(ins_Load(tree->TypeGet()), emitActualTypeSize(tree), targetReg, tree); -#endif -} - //------------------------------------------------------------------------ // genCodeForArrIndex: Generates code to bounds check the index for one dimension of an array reference, // producing the effective index by subtracting the lower bound. @@ -1770,9 +1742,13 @@ void CodeGen::genCodeForIndir(GenTreeIndir* tree) } #endif // FEATURE_SIMD - var_types type = tree->TypeGet(); - instruction ins = ins_Load(type); - regNumber targetReg = tree->GetRegNum(); + var_types type = tree->TypeGet(); + instruction ins = ins_Load(type); +#ifdef TARGET_ARM64 + regNumber targetReg = tree->IsUnusedValue() ? REG_ZR : tree->GetRegNum(); +#else + regNumber targetReg = tree->GetRegNum(); +#endif genConsumeAddress(tree->Addr()); diff --git a/src/coreclr/jit/codegenloongarch64.cpp b/src/coreclr/jit/codegenloongarch64.cpp index 21a3755f8a2ac1..6f8e72a036bdfa 100644 --- a/src/coreclr/jit/codegenloongarch64.cpp +++ b/src/coreclr/jit/codegenloongarch64.cpp @@ -5592,10 +5592,6 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genCodeForPhysReg(treeNode->AsPhysReg()); break; - case GT_NULLCHECK: - genCodeForNullCheck(treeNode->AsIndir()); - break; - case GT_CATCH_ARG: noway_assert(handlerGetsXcptnObj(compiler->compCurBB->bbCatchTyp)); @@ -6439,24 +6435,6 @@ void CodeGen::genCodeForPhysReg(GenTreePhysReg* tree) genProduceReg(tree); } -//--------------------------------------------------------------------- -// genCodeForNullCheck - generate code for a GT_NULLCHECK node -// -// Arguments -// tree - the GT_NULLCHECK node -// -// Return value: -// None -// -void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) -{ - assert(tree->OperIs(GT_NULLCHECK)); - - genConsumeRegs(tree->gtOp1); - - GetEmitter()->emitInsLoadStoreOp(ins_Load(tree->TypeGet()), emitActualTypeSize(tree), REG_R0, tree); -} - //------------------------------------------------------------------------ // genCodeForArrIndex: Generates code to bounds check the index for one dimension of an array reference, // producing the effective index by subtracting the lower bound. @@ -6829,7 +6807,7 @@ void CodeGen::genCodeForIndir(GenTreeIndir* tree) var_types type = tree->TypeGet(); instruction ins = ins_Load(type); instruction ins2 = INS_none; - regNumber targetReg = tree->GetRegNum(); + regNumber targetReg = tree->IsUnusedValue() ? REG_R0 : tree->GetRegNum(); regNumber tmpReg = targetReg; emitAttr attr = emitActualTypeSize(type); int offset = 0; diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 1f7b777f852887..2577a77fe73347 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -1873,10 +1873,6 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genCodeForPhysReg(treeNode->AsPhysReg()); break; - case GT_NULLCHECK: - genCodeForNullCheck(treeNode->AsIndir()); - break; - case GT_CATCH_ARG: noway_assert(handlerGetsXcptnObj(compiler->compCurBB->bbCatchTyp)); @@ -4216,24 +4212,6 @@ void CodeGen::genCodeForPhysReg(GenTreePhysReg* tree) genProduceReg(tree); } -//--------------------------------------------------------------------- -// genCodeForNullCheck - generate code for a GT_NULLCHECK node -// -// Arguments -// tree - the GT_NULLCHECK node -// -// Return value: -// None -// -void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) -{ - assert(tree->OperIs(GT_NULLCHECK)); - - assert(tree->gtOp1->isUsedFromReg()); - regNumber reg = genConsumeReg(tree->gtOp1); - GetEmitter()->emitIns_AR_R(INS_cmp, emitTypeSize(tree), reg, reg, 0); -} - //------------------------------------------------------------------------ // genCodeForArrIndex: Generates code to bounds check the index for one dimension of an array reference, // producing the effective index by subtracting the lower bound. @@ -5126,13 +5104,51 @@ void CodeGen::genCodeForIndir(GenTreeIndir* tree) else { genConsumeAddress(addr); - instruction loadIns = tree->DontExtend() ? INS_mov : ins_Load(targetType); - emit->emitInsLoadInd(loadIns, emitTypeSize(tree), tree->GetRegNum(), tree); + + if (tree->IsUnusedValue()) + { + genEmitCodeForUnusedIndir(tree); + } + else + { + instruction loadIns = tree->DontExtend() ? INS_mov : ins_Load(targetType); + emit->emitInsLoadInd(loadIns, emitTypeSize(tree), tree->GetRegNum(), tree); + } } genProduceReg(tree); } +//--------------------------------------------------------------------- +// genEmitCodeForUnusedIndir - emit code for an unused indirection +// +// Arguments +// tree - the indirection +// +// Return value: +// None +// +void CodeGen::genEmitCodeForUnusedIndir(GenTreeIndir* tree) +{ + GenTree* addr = tree->Addr(); + if (!addr->isContained()) + { + GetEmitter()->emitIns_AR_R(INS_cmp, emitTypeSize(tree), REG_EAX, addr->GetRegNum(), 0); + return; + } + + if (addr->OperIsLocalAddr()) + { + GenTreeLclVarCommon* varNode = addr->AsLclVarCommon(); + unsigned offset = varNode->GetLclOffs(); + GetEmitter()->emitIns_S_R(INS_cmp, emitTypeSize(tree), REG_EAX, varNode->GetLclNum(), offset); + return; + } + + assert(addr->OperIsAddrMode() || addr->IsCnsIntOrI()); + GetEmitter()->emitIns_R_A(INS_cmp, emitTypeSize(tree), REG_EAX, tree); +} + //------------------------------------------------------------------------ // genCodeForStoreInd: Produce code for a GT_STOREIND node. // diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 573708a6c7bef1..ac3bbf97aa7604 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -9863,7 +9863,7 @@ var_types Compiler::gtTypeForNullCheck(GenTree* tree) void Compiler::gtChangeOperToNullCheck(GenTree* tree, BasicBlock* block) { assert(tree->OperIs(GT_FIELD, GT_IND, GT_OBJ, GT_BLK)); - tree->ChangeOper(GT_NULLCHECK); + tree->ChangeOper(GT_IND); tree->ChangeType(gtTypeForNullCheck(tree)); block->bbFlags |= BBF_HAS_NULLCHECK; optMethodFlags |= OMF_HAS_NULLCHECK; diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index a3b4dba68ccf11..fa04c8a0ab3a55 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -6962,21 +6962,19 @@ class Compiler OPK_NULLCHECK }; - typedef JitHashTable, GenTree*> LocalNumberToNullCheckTreeMap; + typedef JitHashTable, GenTreeIndir*> LocalNumberToNullCheckTreeMap; GenTree* getArrayLengthFromAllocation(GenTree* tree DEBUGARG(BasicBlock* block)); GenTree* optPropGetValueRec(unsigned lclNum, unsigned ssaNum, optPropKind valueKind, int walkDepth); GenTree* optPropGetValue(unsigned lclNum, unsigned ssaNum, optPropKind valueKind); GenTree* optEarlyPropRewriteTree(GenTree* tree, LocalNumberToNullCheckTreeMap* nullCheckMap); - bool optDoEarlyPropForBlock(BasicBlock* block); - bool optDoEarlyPropForFunc(); PhaseStatus optEarlyProp(); bool optFoldNullCheck(GenTree* tree, LocalNumberToNullCheckTreeMap* nullCheckMap); - GenTree* optFindNullCheckToFold(GenTree* tree, LocalNumberToNullCheckTreeMap* nullCheckMap); - bool optIsNullCheckFoldingLegal(GenTree* tree, - GenTree* nullCheckTree, - GenTree** nullCheckParent, - Statement** nullCheckStmt); + GenTreeIndir* optFindNullCheckToFold(GenTree* tree, LocalNumberToNullCheckTreeMap* nullCheckMap); + bool optIsNullCheckFoldingLegal(GenTree* tree, + GenTreeIndir* nullCheckTree, + GenTree** nullCheckParent, + Statement** nullCheckStmt); bool optCanMoveNullCheckPastTree(GenTree* tree, unsigned nullCheckLclNum, bool isInsideTry, @@ -10859,7 +10857,6 @@ class GenTreeVisitor case GT_INIT_VAL: case GT_JTRUE: case GT_SWITCH: - case GT_NULLCHECK: case GT_PUTARG_REG: case GT_PUTARG_STK: case GT_RETURNTRAP: diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index d8ba65821e7b1e..2ee04ea54b786d 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -1294,12 +1294,11 @@ inline GenTreeIndir* Compiler::gtNewIndir(var_types typ, GenTree* addr) // basicBlock - Basic block of the node // // Return Value: -// New GT_NULLCHECK node - +// New GT_IND node inline GenTree* Compiler::gtNewNullCheck(GenTree* addr, BasicBlock* basicBlock) { assert(fgAddrCouldBeNull(addr)); - GenTree* nullCheck = gtNewOperNode(GT_NULLCHECK, TYP_BYTE, addr); + GenTree* nullCheck = gtNewOperNode(GT_IND, TYP_BYTE, addr); nullCheck->gtFlags |= GTF_EXCEPT; basicBlock->bbFlags |= BBF_HAS_NULLCHECK; optMethodFlags |= OMF_HAS_NULLCHECK; @@ -4235,7 +4234,6 @@ void GenTree::VisitOperands(TVisitor visitor) case GT_ARR_ADDR: case GT_JTRUE: case GT_SWITCH: - case GT_NULLCHECK: case GT_PUTARG_REG: case GT_PUTARG_STK: #if FEATURE_ARG_SPLIT diff --git a/src/coreclr/jit/earlyprop.cpp b/src/coreclr/jit/earlyprop.cpp index 3adaf9e5fd0f25..829c5497aa49c5 100644 --- a/src/coreclr/jit/earlyprop.cpp +++ b/src/coreclr/jit/earlyprop.cpp @@ -15,23 +15,6 @@ #include "jitpch.h" #include "ssabuilder.h" -bool Compiler::optDoEarlyPropForFunc() -{ - // TODO-MDArray: bool propMDArrayLen = (optMethodFlags & OMF_HAS_MDNEWARRAY) && (optMethodFlags & - // OMF_HAS_MDARRAYREF); - bool propArrayLen = (optMethodFlags & OMF_HAS_NEWARRAY) && (optMethodFlags & OMF_HAS_ARRAYREF); - bool propNullCheck = (optMethodFlags & OMF_HAS_NULLCHECK) != 0; - return propArrayLen || propNullCheck; -} - -bool Compiler::optDoEarlyPropForBlock(BasicBlock* block) -{ - // TODO-MDArray: bool bbHasMDArrayRef = (block->bbFlags & BBF_HAS_MD_IDX_LEN) != 0; - bool bbHasArrayRef = (block->bbFlags & BBF_HAS_IDX_LEN) != 0; - bool bbHasNullCheck = (block->bbFlags & BBF_HAS_NULLCHECK) != 0; - return bbHasArrayRef || bbHasNullCheck; -} - #ifdef DEBUG //----------------------------------------------------------------------------- // optCheckFlagsAreSet: Check that the method flag and the basic block flag are set. @@ -87,33 +70,18 @@ void Compiler::optCheckFlagsAreSet(unsigned methodFlag, // GT_ARR_LENGTH node will then be rewritten to a GT_CNS_INT node if the array length is // constant. // -// Null check folding tries to find GT_INDIR(obj + const) that GT_NULLCHECK(obj) can be folded into -// and removed. Currently, the algorithm only matches GT_INDIR and GT_NULLCHECK in the same basic block. +// Null check folding tries to find GT_INDIR(obj + const) that a previous unused indir can be folded into +// and removed. Currently, the algorithm only matches indirs in the same basic block. // // TODO: support GT_MDARR_LENGTH, GT_MDARRAY_LOWER_BOUND // PhaseStatus Compiler::optEarlyProp() { - if (!optDoEarlyPropForFunc()) - { - // We perhaps should verify the OMF are set properly - // - JITDUMP("no arrays or null checks in the method\n"); - return PhaseStatus::MODIFIED_NOTHING; - } - assert(fgSsaPassesCompleted == 1); unsigned numChanges = 0; for (BasicBlock* const block : Blocks()) { -#ifndef DEBUG - if (!optDoEarlyPropForBlock(block)) - { - continue; - } -#endif - compCurBB = block; CompAllocator allocator(getAllocator(CMK_EarlyProp)); @@ -144,7 +112,7 @@ PhaseStatus Compiler::optEarlyProp() if (isRewritten) { // Make sure the transformation happens in debug, check, and release build. - assert(optDoEarlyPropForFunc() && optDoEarlyPropForBlock(block)); + // assert(optDoEarlyPropForFunc() && optDoEarlyPropForBlock(block)); gtSetStmtInfo(stmt); fgSetStmtSeq(stmt); numChanges++; @@ -199,16 +167,6 @@ GenTree* Compiler::optEarlyPropRewriteTree(GenTree* tree, LocalNumberToNullCheck { return folded ? tree : nullptr; } -#ifdef DEBUG - else - { - if (propKind == optPropKind::OPK_ARRAYLEN) - { - optCheckFlagsAreSet(OMF_HAS_ARRAYREF, "OMF_HAS_ARRAYREF", BBF_HAS_IDX_LEN, "BBF_HAS_IDX_LEN", tree, - compCurBB); - } - } -#endif unsigned lclNum = objectRefPtr->AsLclVarCommon()->GetLclNum(); unsigned ssaNum = objectRefPtr->AsLclVarCommon()->GetSsaNum(); @@ -404,8 +362,9 @@ GenTree* Compiler::optPropGetValueRec(unsigned lclNum, unsigned ssaNum, optPropK } //---------------------------------------------------------------- -// optFoldNullChecks: Try to find a GT_NULLCHECK node that can be folded into the indirection node mark it for removal -// if possible. +// optFoldNullChecks: +// Try to find a previous unused indirection node that can be folded into a +// current indirection node and mark it for removal if possible. // // Arguments: // tree - The input indirection tree. @@ -415,36 +374,18 @@ GenTree* Compiler::optPropGetValueRec(unsigned lclNum, unsigned ssaNum, optPropK // true if a null check was folded // // Notes: -// If a GT_NULLCHECK node is post-dominated by an indirection node on the same local and the trees between -// the GT_NULLCHECK and the indirection don't have unsafe side effects, the GT_NULLCHECK can be removed. -// The indir will cause a NullReferenceException if and only if GT_NULLCHECK will cause the same -// NullReferenceException. - +// If an unused indirection node is post-dominated by an indirection node on the same local and the trees between +// the two indirections don't have unsafe side effects, the unused indirection can be removed. +// bool Compiler::optFoldNullCheck(GenTree* tree, LocalNumberToNullCheckTreeMap* nullCheckMap) { -#ifdef DEBUG - if (tree->OperGet() == GT_NULLCHECK) - { - optCheckFlagsAreSet(OMF_HAS_NULLCHECK, "OMF_HAS_NULLCHECK", BBF_HAS_NULLCHECK, "BBF_HAS_NULLCHECK", tree, - compCurBB); - } -#else - if ((compCurBB->bbFlags & BBF_HAS_NULLCHECK) == 0) - { - return false; - } -#endif - - GenTree* nullCheckTree = optFindNullCheckToFold(tree, nullCheckMap); - GenTree* nullCheckParent = nullptr; - Statement* nullCheckStmt = nullptr; - bool folded = false; + GenTreeIndir* nullCheckTree = optFindNullCheckToFold(tree, nullCheckMap); + GenTree* nullCheckParent = nullptr; + Statement* nullCheckStmt = nullptr; + bool folded = false; if ((nullCheckTree != nullptr) && optIsNullCheckFoldingLegal(tree, nullCheckTree, &nullCheckParent, &nullCheckStmt)) { #ifdef DEBUG - // Make sure the transformation happens in debug, check, and release build. - assert(optDoEarlyPropForFunc() && optDoEarlyPropForBlock(compCurBB) && - (compCurBB->bbFlags & BBF_HAS_NULLCHECK) != 0); if (verbose) { printf("optEarlyProp Marking a null check for removal\n"); @@ -464,39 +405,48 @@ bool Compiler::optFoldNullCheck(GenTree* tree, LocalNumberToNullCheckTreeMap* nu nullCheckParent->gtFlags &= ~GTF_DONT_CSE; } - nullCheckMap->Remove(nullCheckTree->gtGetOp1()->AsLclVarCommon()->GetLclNum()); + nullCheckMap->Remove(nullCheckTree->AsIndir()->Addr()->AsLclVarCommon()->GetLclNum()); // Re-morph the statement. Statement* curStmt = compCurStmt; - fgMorphBlockStmt(compCurBB, nullCheckStmt DEBUGARG("optFoldNullCheck")); - compCurStmt = curStmt; + bool removed = fgMorphBlockStmt(compCurBB, nullCheckStmt DEBUGARG("optFoldNullCheck")); + compCurStmt = curStmt; + + // Verify that node was removed + assert(removed || (gtFindLink(nullCheckStmt, nullCheckTree).result == nullptr)); folded = true; } - if ((tree->OperGet() == GT_NULLCHECK) && (tree->gtGetOp1()->OperGet() == GT_LCL_VAR)) + bool isRemovableIndir = tree->OperIsIndir() && !tree->OperIsStore(); + + if (isRemovableIndir && tree->AsIndir()->Addr()->OperIs(GT_LCL_VAR) && ((tree->gtFlags & GTF_IND_NONFAULTING) == 0)) { - nullCheckMap->Set(tree->gtGetOp1()->AsLclVarCommon()->GetLclNum(), tree, - LocalNumberToNullCheckTreeMap::SetKind::Overwrite); + if (tree->gtIsUnusedValue()) + { + nullCheckMap->Set(tree->AsIndir()->Addr()->AsLclVarCommon()->GetLclNum(), tree->AsIndir(), + LocalNumberToNullCheckTreeMap::SetKind::Overwrite); + } } return folded; } //---------------------------------------------------------------- -// optFindNullCheckToFold: Try to find a GT_NULLCHECK node that can be folded into the indirection node. +// optFindNullCheckToFold: Try to find a previous unused indirection that can +// be folded into the indirection node. // // Arguments: // tree - The input indirection tree. -// nullCheckMap - Map of the local numbers to the latest NULLCHECKs on those locals in the current basic block +// nullCheckMap - Map of the local numbers to the latest nullchecks on those locals in the current basic block // // Notes: // Check for cases where // 1. One of the following trees // -// nullcheck(x) +// indir(x) (unused) // or -// x = comma(nullcheck(y), add(y, const1)) +// x = comma(indir(y), add(y, const1)) // // is post-dominated in the same basic block by one of the following trees // @@ -507,8 +457,7 @@ bool Compiler::optFoldNullCheck(GenTree* tree, LocalNumberToNullCheckTreeMap* nu // (indir is any node for which OperIsIndirOrArrMetaData() is true.) // // 2. const1 + const2 if sufficiently small. - -GenTree* Compiler::optFindNullCheckToFold(GenTree* tree, LocalNumberToNullCheckTreeMap* nullCheckMap) +GenTreeIndir* Compiler::optFindNullCheckToFold(GenTree* tree, LocalNumberToNullCheckTreeMap* nullCheckMap) { assert(tree->OperIsIndirOrArrMetaData()); @@ -536,14 +485,14 @@ GenTree* Compiler::optFindNullCheckToFold(GenTree* tree, LocalNumberToNullCheckT } const unsigned lclNum = lclVarNode->GetLclNum(); - GenTree* nullCheckTree = nullptr; + GenTreeIndir* nullCheckTree = nullptr; unsigned nullCheckLclNum = BAD_VAR_NUM; // Check if we saw a nullcheck on this local in this basic block - // This corresponds to nullcheck(x) tree in the header comment. + // This corresponds to the unused indir(x) tree in the header comment. if (nullCheckMap->Lookup(lclNum, &nullCheckTree)) { - GenTree* nullCheckAddr = nullCheckTree->AsIndir()->Addr(); + GenTree* nullCheckAddr = nullCheckTree->Addr(); if ((nullCheckAddr->OperGet() != GT_LCL_VAR) || (nullCheckAddr->AsLclVarCommon()->GetSsaNum() != ssaNum)) { nullCheckTree = nullptr; @@ -556,7 +505,7 @@ GenTree* Compiler::optFindNullCheckToFold(GenTree* tree, LocalNumberToNullCheckT if (nullCheckTree == nullptr) { - // Check if we have x = comma(nullcheck(y), add(y, const1)) pattern. + // Check if we have x = comma(ind(y), add(y, const1)) pattern. // Find the definition of the indirected local ('x' in the pattern above). LclSsaVarDsc* defLoc = lvaTable[lclNum].GetPerSsaData(ssaNum); @@ -576,14 +525,14 @@ GenTree* Compiler::optFindNullCheckToFold(GenTree* tree, LocalNumberToNullCheckT const bool commaOnly = true; GenTree* commaOp1EffectiveValue = defRHS->gtGetOp1()->gtEffectiveVal(commaOnly); - if (commaOp1EffectiveValue->OperGet() != GT_NULLCHECK) + if (!commaOp1EffectiveValue->OperIsIndir() || commaOp1EffectiveValue->OperIsStore()) { return nullptr; } - GenTree* nullCheckAddress = commaOp1EffectiveValue->gtGetOp1(); + GenTree* nullCheckAddress = commaOp1EffectiveValue->AsIndir()->Addr(); - if ((nullCheckAddress->OperGet() != GT_LCL_VAR) || (defRHS->gtGetOp2()->OperGet() != GT_ADD)) + if (!nullCheckAddress->OperIs(GT_LCL_VAR) || (defRHS->gtGetOp2()->OperGet() != GT_ADD)) { return nullptr; } @@ -598,7 +547,7 @@ GenTree* Compiler::optFindNullCheckToFold(GenTree* tree, LocalNumberToNullCheckT (additionOp2->IsCnsIntOrI())) { offsetValue += additionOp2->AsIntConCommon()->IconValue(); - nullCheckTree = commaOp1EffectiveValue; + nullCheckTree = commaOp1EffectiveValue->AsIndir(); } } @@ -613,23 +562,24 @@ GenTree* Compiler::optFindNullCheckToFold(GenTree* tree, LocalNumberToNullCheckT } //---------------------------------------------------------------- -// optIsNullCheckFoldingLegal: Check the nodes between the GT_NULLCHECK node and the indirection to determine -// if null check folding is legal. +// optIsNullCheckFoldingLegal: +// Check the nodes between the two indirs to determine if null check folding +// is legal. // // Arguments: // tree - The input indirection tree. -// nullCheckTree - The GT_NULLCHECK tree that is a candidate for removal. -// nullCheckParent - The parent of the GT_NULLCHECK tree that is a candidate for removal (out-parameter). -// nullCheckStatement - The statement of the GT_NULLCHECK tree that is a candidate for removal (out-parameter). - -bool Compiler::optIsNullCheckFoldingLegal(GenTree* tree, - GenTree* nullCheckTree, - GenTree** nullCheckParent, - Statement** nullCheckStmt) +// nullCheckTree - The unused indirection tree that is a candidate for removal. +// nullCheckParent - The parent of nullCheckTree (out-parameter). +// nullCheckStatement - The statement nullCheckTree (out-parameter). + +bool Compiler::optIsNullCheckFoldingLegal(GenTree* tree, + GenTreeIndir* nullCheckTree, + GenTree** nullCheckParent, + Statement** nullCheckStmt) { - // Check all nodes between the GT_NULLCHECK and the indirection to see - // if any nodes have unsafe side effects. - unsigned nullCheckLclNum = nullCheckTree->gtGetOp1()->AsLclVarCommon()->GetLclNum(); + // Check all nodes between the indirs to see if any nodes have unsafe side + // effects. + unsigned nullCheckLclNum = nullCheckTree->Addr()->AsLclVarCommon()->GetLclNum(); bool isInsideTry = compCurBB->hasTryIndex(); bool canRemoveNullCheck = true; const unsigned maxNodesWalked = 50; @@ -661,15 +611,14 @@ bool Compiler::optIsNullCheckFoldingLegal(GenTree* tree, if (currentTree == tree) { - // The GT_NULLCHECK and the indirection are in the same statements. + // The indirs are in the same statements. *nullCheckStmt = compCurStmt; } else { - // The GT_NULLCHECK and the indirection are in different statements. - // Walk the nodes in the statement containing the indirection - // in reverse execution order starting with the indirection's - // predecessor. + // The indirs are in different statements. Walk the nodes in the + // statement containing the indirection in reverse execution order + // starting with the indirection's predecessor. GenTree* nullCheckStatementRoot = previousTree; currentTree = tree->gtPrev; while (canRemoveNullCheck && (currentTree != nullptr)) @@ -723,7 +672,7 @@ bool Compiler::optIsNullCheckFoldingLegal(GenTree* tree, // // Arguments: // tree - The tree to check. -// nullCheckLclNum - The local variable that GT_NULLCHECK checks. +// nullCheckLclNum - The local variable that the null check checks. // isInsideTry - True if tree is inside try, false otherwise. // checkSideEffectSummary -If true, check side effect summary flags only, // otherwise check the side effects of the operation itself. diff --git a/src/coreclr/jit/emitxarch.cpp b/src/coreclr/jit/emitxarch.cpp index cbba666f80eb7b..a299f5781c3093 100644 --- a/src/coreclr/jit/emitxarch.cpp +++ b/src/coreclr/jit/emitxarch.cpp @@ -3342,7 +3342,7 @@ void emitter::spillIntArgRegsToShadowSlots() // void emitter::emitInsLoadInd(instruction ins, emitAttr attr, regNumber dstReg, GenTreeIndir* mem) { - assert(mem->OperIs(GT_IND, GT_NULLCHECK)); + assert(mem->OperIs(GT_IND)); GenTree* addr = mem->Addr(); @@ -5024,7 +5024,7 @@ void emitter::emitIns_R_A(instruction ins, emitAttr attr, regNumber reg1, GenTre id->idIns(ins); id->idReg1(reg1); - emitHandleMemOp(indir, id, IF_RRW_ARD, ins); + emitHandleMemOp(indir, id, ins == INS_cmp ? IF_RRD_ARD : IF_RRW_ARD, ins); UNATIVE_OFFSET sz = emitInsSizeAM(id, insCodeRM(ins)); id->idCodeSize(sz); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index e75b1a94889f43..b7d845734aea81 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -6005,7 +6005,6 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse) case GT_INIT_VAL: case GT_JTRUE: case GT_SWITCH: - case GT_NULLCHECK: case GT_PUTARG_REG: case GT_PUTARG_STK: case GT_RETURNTRAP: @@ -6303,6 +6302,29 @@ GenTree* GenTree::gtGetParent(GenTree*** pUse) return user; } +bool GenTree::gtIsUnusedValue() +{ + assert(IsValue()); + + GenTree* par = gtGetParent(nullptr); + if (par == nullptr) + { + return true; + } + + if (par->OperIs(GT_COMMA)) + { + if ((par->gtGetOp1() == this) || par->TypeIs(TYP_VOID)) + { + return true; + } + + return par->gtIsUnusedValue(); + } + + return false; +} + //------------------------------------------------------------------------------ // OperRequiresAsgFlag : Check whether the operation requires GTF_ASG flag regardless // of the children's flags. @@ -6497,7 +6519,6 @@ ExceptionSetFlags GenTree::OperExceptions(Compiler* comp) case GT_IND: case GT_BLK: case GT_OBJ: - case GT_NULLCHECK: case GT_STORE_BLK: case GT_STORE_DYN_BLK: if (((this->gtFlags & GTF_IND_NONFAULTING) == 0) && comp->fgAddrCouldBeNull(this->AsIndir()->Addr())) @@ -6725,7 +6746,6 @@ GenTree::VtablePtr GenTree::GetVtableForOper(genTreeOps oper) break; case GT_IND: - case GT_NULLCHECK: { GenTreeIndir gt; res = *reinterpret_cast(>); @@ -9314,7 +9334,6 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) case GT_INIT_VAL: case GT_JTRUE: case GT_SWITCH: - case GT_NULLCHECK: case GT_PUTARG_REG: case GT_PUTARG_STK: case GT_BSWAP: diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 25aaf4541c7aaf..2b9c7bdc5d487d 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -907,7 +907,7 @@ struct GenTree // This returns true only for GT_IND and GT_STOREIND, and is used in contexts where a "true" // indirection is expected (i.e. either a load to or a store from a single register). - // OperIsIndir() returns true also for indirection nodes such as GT_BLK, etc. as well as GT_NULLCHECK. + // OperIsIndir() returns true also for indirection nodes such as GT_BLK, etc. bool isIndir() const; bool isContainedIntOrIImmed() const @@ -1570,7 +1570,7 @@ struct GenTree // OperIsIndir() returns true also for indirection nodes such as GT_BLK, etc. as well as GT_NULLCHECK. static bool OperIsIndir(genTreeOps gtOper) { - return gtOper == GT_IND || gtOper == GT_STOREIND || gtOper == GT_NULLCHECK || OperIsBlk(gtOper); + return gtOper == GT_IND || gtOper == GT_STOREIND || OperIsBlk(gtOper); } static bool OperIsArrLength(genTreeOps gtOper) @@ -1829,6 +1829,7 @@ struct GenTree public: GenTree* gtGetParent(GenTree*** pUse); + bool gtIsUnusedValue(); void ReplaceOperand(GenTree** useEdge, GenTree* replacement); diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index 56ea06b2a061bd..573c439caaa793 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -88,7 +88,6 @@ GTNODE(STORE_OBJ , GenTreeObj ,0,GTK_BINOP|GTK_EXOP|GTK_NOVALUE) GTNODE(BLK , GenTreeBlk ,0,GTK_UNOP|GTK_EXOP) // Block/object with no gc pointers, and with a known size (e.g. a struct with no gc fields) GTNODE(STORE_BLK , GenTreeBlk ,0,GTK_BINOP|GTK_EXOP|GTK_NOVALUE) // Block/object with no gc pointers, and with a known size (e.g. a struct with no gc fields) GTNODE(STORE_DYN_BLK , GenTreeStoreDynBlk ,0,GTK_SPECIAL|GTK_NOVALUE) // Dynamically sized block store -GTNODE(NULLCHECK , GenTreeIndir ,0,GTK_UNOP|GTK_NOVALUE) // Null checks the source GTNODE(ARR_LENGTH , GenTreeArrLen ,0,GTK_UNOP|GTK_EXOP) // single-dimension (SZ) array length GTNODE(MDARR_LENGTH , GenTreeMDArr ,0,GTK_UNOP|GTK_EXOP) // multi-dimension (MD) array length of a specific dimension diff --git a/src/coreclr/jit/gtstructs.h b/src/coreclr/jit/gtstructs.h index 7d50adbca39f97..72214f04ddc98d 100644 --- a/src/coreclr/jit/gtstructs.h +++ b/src/coreclr/jit/gtstructs.h @@ -100,7 +100,7 @@ GTSTRUCT_1(Qmark , GT_QMARK) GTSTRUCT_1(PhiArg , GT_PHI_ARG) GTSTRUCT_1(Phi , GT_PHI) GTSTRUCT_1(StoreInd , GT_STOREIND) -GTSTRUCT_N(Indir , GT_STOREIND, GT_IND, GT_NULLCHECK, GT_BLK, GT_STORE_BLK, GT_OBJ, GT_STORE_OBJ, GT_STORE_DYN_BLK) +GTSTRUCT_N(Indir , GT_STOREIND, GT_IND, GT_BLK, GT_STORE_BLK, GT_OBJ, GT_STORE_OBJ, GT_STORE_DYN_BLK) GTSTRUCT_N(Conditional , GT_SELECT) #if FEATURE_ARG_SPLIT GTSTRUCT_2_SPECIAL(PutArgStk, GT_PUTARG_STK, GT_PUTARG_SPLIT) diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index 8b499069c78f1c..287046aebcff0e 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -2271,8 +2271,6 @@ bool Compiler::fgRemoveDeadStore(GenTree** pTree, } if (nextNode->OperIsIndir()) { - // This must be a non-nullcheck form of indir, or it would not be a def. - assert(nextNode->OperGet() != GT_NULLCHECK); if (nextNode->OperIsStore()) { // This is a store, which takes a location and a value to be stored. @@ -2290,7 +2288,7 @@ bool Compiler::fgRemoveDeadStore(GenTree** pTree, } else { - // This is a non-store indirection, and the assignment will com after it. + // This is a non-store indirection, and the assignment will come after it. asgNode = nextNode->gtNext; } } diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 3af37e2c002c38..1d95b04ffaeeb3 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -167,7 +167,6 @@ GenTree* Lowering::LowerNode(GenTree* node) assert(node != nullptr); switch (node->gtOper) { - case GT_NULLCHECK: case GT_IND: LowerIndir(node->AsIndir()); break; @@ -7157,28 +7156,22 @@ void Lowering::LowerStoreIndirCommon(GenTreeStoreInd* ind) } //------------------------------------------------------------------------ -// LowerIndir: a common logic to lower IND load or NullCheck. +// LowerIndir: common logic to lower IND nodes. // // Arguments: // ind - the ind node we are lowering. // void Lowering::LowerIndir(GenTreeIndir* ind) { - assert(ind->OperIs(GT_IND, GT_NULLCHECK)); + assert(ind->OperIs(GT_IND)); // Process struct typed indirs separately unless they are unused; // they only appear as the source of a block copy operation or a return node. if (!ind->TypeIs(TYP_STRUCT) || ind->IsUnusedValue()) { -#ifndef TARGET_XARCH - // On non-xarch, whether or not we can contain an address mode will depend on the access width - // which may be changed when transforming an unused indir, so do that first. - // On xarch, it is the opposite: we transform to indir/nullcheck based on whether we contained the - // address mode, so in that case we must do this transformation last. - if (ind->OperIs(GT_NULLCHECK) || ind->IsUnusedValue()) + if (ind->IsUnusedValue()) { TransformUnusedIndirection(ind, comp, m_block); } -#endif // TODO-Cleanup: We're passing isContainable = true but ContainCheckIndir rejects // address containment in some cases so we end up creating trivial (reg + offfset) @@ -7195,13 +7188,6 @@ void Lowering::LowerIndir(GenTreeIndir* ind) TryCreateAddrMode(ind->Addr(), isContainable, ind); ContainCheckIndir(ind); - -#ifdef TARGET_XARCH - if (ind->OperIs(GT_NULLCHECK) || ind->IsUnusedValue()) - { - TransformUnusedIndirection(ind, comp, m_block); - } -#endif } else { @@ -7222,47 +7208,10 @@ void Lowering::LowerIndir(GenTreeIndir* ind) // void Lowering::TransformUnusedIndirection(GenTreeIndir* ind, Compiler* comp, BasicBlock* block) { - // A nullcheck is essentially the same as an indirection with no use. - // The difference lies in whether a target register must be allocated. - // On XARCH we can generate a compare with no target register as long as the address - // is not contained. - // On ARM64 we can generate a load to REG_ZR in all cases. - // However, on ARM we must always generate a load to a register. - // In the case where we require a target register, it is better to use GT_IND, since - // GT_NULLCHECK is a non-value node and would therefore require an internal register - // to use as the target. That is non-optimal because it will be modeled as conflicting - // with the source register(s). - // So, to summarize: - // - On ARM64, always use GT_NULLCHECK for a dead indirection. - // - On ARM, always use GT_IND. - // - On XARCH, use GT_IND if we have a contained address, and GT_NULLCHECK otherwise. - // In all cases we try to preserve the original type and never make it wider to avoid AVEs. - // For structs we conservatively lower it to BYTE. For 8-byte primitives we lower it to TYP_INT - // on XARCH as an optimization. - // - assert(ind->OperIs(GT_NULLCHECK, GT_IND, GT_BLK, GT_OBJ)); + assert(ind->OperIs(GT_IND, GT_BLK, GT_OBJ)); + ind->ChangeOper(GT_IND); ind->ChangeType(comp->gtTypeForNullCheck(ind)); - -#if defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) - bool useNullCheck = true; -#elif TARGET_ARM - bool useNullCheck = false; -#else // TARGET_XARCH - bool useNullCheck = !ind->Addr()->isContained(); - ind->ClearDontExtend(); -#endif // !TARGET_XARCH - - if (useNullCheck && !ind->OperIs(GT_NULLCHECK)) - { - comp->gtChangeOperToNullCheck(ind, block); - ind->ClearUnusedValue(); - } - else if (!useNullCheck && !ind->OperIs(GT_IND)) - { - ind->ChangeOper(GT_IND); - ind->SetUnusedValue(); - } } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/lsraarm.cpp b/src/coreclr/jit/lsraarm.cpp index 06d807772f36cd..3c66fafdad54b5 100644 --- a/src/coreclr/jit/lsraarm.cpp +++ b/src/coreclr/jit/lsraarm.cpp @@ -688,17 +688,8 @@ int LinearScan::BuildNode(GenTree* tree) } break; - case GT_NULLCHECK: -#ifdef TARGET_ARM - // On Arm32 we never want to use GT_NULLCHECK, as we require a target register. - // Previously we used an internal register for this, but that results in a lifetime - // that overlaps with all the source registers. - assert(!"Should never see GT_NULLCHECK on Arm/32"); -#endif - // For Arm64 we simply fall through to the GT_IND case, and will use REG_ZR as the target. - FALLTHROUGH; case GT_IND: - assert(dstCount == (tree->OperIs(GT_NULLCHECK) ? 0 : 1)); + assert(dstCount == 1); srcCount = BuildIndir(tree->AsIndir()); break; diff --git a/src/coreclr/jit/lsraarm64.cpp b/src/coreclr/jit/lsraarm64.cpp index b2c815bb1e396e..43c7f1750d017d 100644 --- a/src/coreclr/jit/lsraarm64.cpp +++ b/src/coreclr/jit/lsraarm64.cpp @@ -772,9 +772,8 @@ int LinearScan::BuildNode(GenTree* tree) } break; - case GT_NULLCHECK: case GT_IND: - assert(dstCount == (tree->OperIs(GT_NULLCHECK) ? 0 : 1)); + assert(dstCount == 1); srcCount = BuildIndir(tree->AsIndir()); break; diff --git a/src/coreclr/jit/lsraarmarch.cpp b/src/coreclr/jit/lsraarmarch.cpp index 2c42d385758bfd..cfeb05cd6518fd 100644 --- a/src/coreclr/jit/lsraarmarch.cpp +++ b/src/coreclr/jit/lsraarmarch.cpp @@ -116,7 +116,13 @@ int LinearScan::BuildIndir(GenTreeIndir* indirTree) int srcCount = BuildIndirUses(indirTree); buildInternalRegisterUses(); - if (!indirTree->OperIs(GT_STOREIND, GT_NULLCHECK)) + bool buildDef = !indirTree->OperIs(GT_STOREIND); +#ifdef TARGET_ARM64 + // On arm64 we can just load into XZR if the indirection is unused. + buildDef &= !indirTree->IsUnusedValue(); +#endif + + if (buildDef) { BuildDef(indirTree); } diff --git a/src/coreclr/jit/lsraloongarch64.cpp b/src/coreclr/jit/lsraloongarch64.cpp index 934d1014d43c42..cd1fe53c508c2f 100644 --- a/src/coreclr/jit/lsraloongarch64.cpp +++ b/src/coreclr/jit/lsraloongarch64.cpp @@ -609,9 +609,8 @@ int LinearScan::BuildNode(GenTree* tree) } break; - case GT_NULLCHECK: case GT_IND: - assert(dstCount == (tree->OperIs(GT_NULLCHECK) ? 0 : 1)); + assert(dstCount == 1); srcCount = BuildIndir(tree->AsIndir()); break; @@ -741,7 +740,7 @@ int LinearScan::BuildIndir(GenTreeIndir* indirTree) int srcCount = BuildIndirUses(indirTree); buildInternalRegisterUses(); - if (!indirTree->OperIs(GT_STOREIND, GT_NULLCHECK)) + if (!indirTree->OperIs(GT_STOREIND) && !indirTree->IsUnusedValue()) { BuildDef(indirTree); } diff --git a/src/coreclr/jit/lsraxarch.cpp b/src/coreclr/jit/lsraxarch.cpp index 3c5220ba1d9140..9f349239945543 100644 --- a/src/coreclr/jit/lsraxarch.cpp +++ b/src/coreclr/jit/lsraxarch.cpp @@ -607,25 +607,6 @@ int LinearScan::BuildNode(GenTree* tree) srcCount = BuildIndir(tree->AsIndir()); break; - case GT_NULLCHECK: - { - assert(dstCount == 0); -#ifdef TARGET_X86 - if (varTypeIsByte(tree)) - { - // on X86 we have to use byte-able regs for byte-wide loads - BuildUse(tree->gtGetOp1(), RBM_BYTE_REGS); - srcCount = 1; - break; - } -#endif - // If we have a contained address on a nullcheck, we transform it to - // an unused GT_IND, since we require a target register. - BuildUse(tree->gtGetOp1()); - srcCount = 1; - break; - } - case GT_IND: srcCount = BuildIndir(tree->AsIndir()); assert(dstCount == 1); @@ -2639,7 +2620,8 @@ int LinearScan::BuildIndir(GenTreeIndir* indirTree) assert(srcCount <= BYTE_REG_COUNT); #endif - if (indirTree->gtOper != GT_STOREIND) + if ((indirTree->gtOper != GT_STOREIND) && + (!indirTree->IsUnusedValue() || indirTree->Addr()->IsIconHandle(GTF_ICON_TLS_HDL))) { BuildDef(indirTree); } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index d99a39cbc566ca..eaf5437ea7a446 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -9988,6 +9988,18 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA return constNode; } } + if (op1->IsCall()) + { + GenTreeCall* const call = op1->AsCall(); + if (call->IsHelperCall() && s_helperCallProperties.NonNullReturn(eeGetHelperNum(call->gtCallMethHnd))) + { + JITDUMP("\nIND on [%06u] is nonfaulting\n", dspTreeID(call)); + tree->gtFlags |= GTF_IND_NONFAULTING; + tree->gtFlags &= ~GTF_EXCEPT; + tree->gtFlags |= op1->gtFlags & GTF_EXCEPT; + } + } + break; case GT_DIV: @@ -10374,24 +10386,6 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA break; #endif - case GT_NULLCHECK: - { - op1 = tree->AsUnOp()->gtGetOp1(); - if (op1->IsCall()) - { - GenTreeCall* const call = op1->AsCall(); - if (call->IsHelperCall() && s_helperCallProperties.NonNullReturn(eeGetHelperNum(call->gtCallMethHnd))) - { - JITDUMP("\nNULLCHECK on [%06u] will always succeed\n", dspTreeID(call)); - - // TODO: Can we also remove the call? - // - return fgMorphTree(call); - } - } - } - break; - default: break; } diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index c34d599d817cb0..626d7663de4ed3 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -7085,15 +7085,6 @@ void Compiler::optHoistLoopBlocks(unsigned loopNum, ArrayStack* blo { return false; } - else if (node->OperIs(GT_NULLCHECK)) - { - // If a null-check is for `this` object, it is safe to - // hoist it out of the loop. Assertionprop will get rid - // of left over nullchecks present inside the loop. Also, - // since NULLCHECK has no value, it will never be CSE, - // hence this check is not present in optIsCSEcandidate(). - return true; - } // Tree must be a suitable CSE candidate for us to be able to hoist it. return m_compiler->optIsCSEcandidate(node); diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index b18ebd746e27c4..07fe60f1315f4b 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -6870,8 +6870,8 @@ void ValueNumStore::vnDumpZeroObj(Compiler* comp, VNFuncApp* zeroObj) // Static fields, methods. static UINT8 vnfOpAttribs[VNF_COUNT]; static genTreeOps genTreeOpsIllegalAsVNFunc[] = {GT_IND, // When we do heap memory. - GT_NULLCHECK, GT_QMARK, GT_COLON, GT_LOCKADD, GT_XADD, GT_XCHG, - GT_CMPXCHG, GT_LCLHEAP, GT_BOX, GT_XORR, GT_XAND, GT_STORE_DYN_BLK, + GT_QMARK, GT_COLON, GT_LOCKADD, GT_XADD, GT_XCHG, GT_CMPXCHG, + GT_LCLHEAP, GT_BOX, GT_XORR, GT_XAND, GT_STORE_DYN_BLK, // These need special semantics: GT_COMMA, // == second argument (but with exception(s) from first). @@ -8916,13 +8916,11 @@ void Compiler::fgValueNumberTree(GenTree* tree) break; } - // These unary nodes do not produce values. Note that for NULLCHECK the - // additional exception will be added below by "fgValueNumberAddExceptionSet". + // These unary nodes do not produce values. case GT_JTRUE: case GT_SWITCH: case GT_RETURN: case GT_RETFILT: - case GT_NULLCHECK: if (tree->gtGetOp1() != nullptr) { tree->gtVNPair = vnStore->VNPWithExc(vnStore->VNPForVoid(), @@ -10878,7 +10876,6 @@ void Compiler::fgValueNumberAddExceptionSet(GenTree* tree) case GT_IND: case GT_BLK: case GT_OBJ: - case GT_NULLCHECK: fgValueNumberAddExceptionSetForIndirection(tree, tree->AsIndir()->Addr()); break;