From b8bdde480478dba70f172a8547961c87fe817d30 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Tue, 21 Apr 2026 02:00:17 +0200 Subject: [PATCH 1/8] Introduce GT_ASSERTION node Synthesize a GT_ASSERTION marker statement at the start of stayInLoopSucc in optTryInvertWhileLoop, recording that the loop guard is known to be true on entry to the loop body. This compensates for cyclic value numbers from phi merges that otherwise prevent later phases (assertion prop, range check) from proving the guard still holds inside the loop body. The node is dropped before LIR by Rationalizer and skipped by IV strength reduction so it does not pin primary IVs. Alternative for #127117. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/assertionprop.cpp | 47 ++++++++++++++++--- src/coreclr/jit/compiler.h | 3 +- src/coreclr/jit/compiler.hpp | 1 + src/coreclr/jit/gentree.cpp | 2 + src/coreclr/jit/gtlist.h | 1 + src/coreclr/jit/inductionvariableopts.cpp | 6 +++ src/coreclr/jit/liveness.cpp | 1 + src/coreclr/jit/optimizer.cpp | 56 +++++++++++++++++++++++ src/coreclr/jit/rationalize.cpp | 8 ++++ src/coreclr/jit/valuenum.cpp | 1 + 10 files changed, 118 insertions(+), 8 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 71d299ee7c0e75..69da3587bfd56c 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1649,7 +1649,7 @@ AssertionIndex Compiler::optCreateJtrueAssertions(GenTree* op1, GenTree* op2, bo return assertionIndex; } -AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) +AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree, bool createComplementary) { // These assertions are VN based, so not relevant for local prop // @@ -1705,7 +1705,10 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) relopFunc = ValueNumStore::SwapRelop(relopFunc); AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBound(this, relopFunc, op2VN, op1VN, 0); AssertionIndex idx = optAddAssertion(dsc); - optCreateComplementaryAssertion(idx); + if (createComplementary) + { + optCreateComplementaryAssertion(idx); + } return idx; } @@ -1714,7 +1717,10 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) { AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBound(this, relopFunc, op1VN, op2VN, 0); AssertionIndex idx = optAddAssertion(dsc); - optCreateComplementaryAssertion(idx); + if (createComplementary) + { + optCreateComplementaryAssertion(idx); + } return idx; } @@ -1727,7 +1733,10 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) relopFunc = ValueNumStore::SwapRelop(relopFunc); AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBound(this, relopFunc, op2VN, checkedBnd, checkedBndCns); AssertionIndex idx = optAddAssertion(dsc); - optCreateComplementaryAssertion(idx); + if (createComplementary) + { + optCreateComplementaryAssertion(idx); + } return idx; } @@ -1736,7 +1745,10 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) { AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBound(this, relopFunc, op1VN, checkedBnd, checkedBndCns); AssertionIndex idx = optAddAssertion(dsc); - optCreateComplementaryAssertion(idx); + if (createComplementary) + { + optCreateComplementaryAssertion(idx); + } return idx; } @@ -1745,6 +1757,14 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) ValueNumStore::UnsignedCompareCheckedBoundInfo unsignedCompareBnd; if (vnStore->IsVNUnsignedCompareCheckedBound(relopVN, &unsignedCompareBnd)) { + if (!createComplementary && (unsignedCompareBnd.cmpOper == VNF_GE_UN)) + { + // The no-throw assertion only holds on the FALSE edge of "i >= bnd". A one-sided + // caller guarantees the relop is TRUE at the use site, so the assertion does not + // hold there - skip it. + return NO_ASSERTION_INDEX; + } + ValueNum idxVN = vnStore->VNNormalValue(unsignedCompareBnd.vnIdx); ValueNum lenVN = vnStore->VNNormalValue(unsignedCompareBnd.vnBound); @@ -1767,7 +1787,10 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) relopFunc = ValueNumStore::SwapRelop(relopFunc); AssertionDsc dsc = AssertionDsc::CreateConstantBound(this, relopFunc, op2VN, op1VN); AssertionIndex idx = optAddAssertion(dsc); - optCreateComplementaryAssertion(idx); + if (createComplementary) + { + optCreateComplementaryAssertion(idx); + } return idx; } @@ -1775,7 +1798,10 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) { AssertionDsc dsc = AssertionDsc::CreateConstantBound(this, relopFunc, op1VN, op2VN); AssertionIndex idx = optAddAssertion(dsc); - optCreateComplementaryAssertion(idx); + if (createComplementary) + { + optCreateComplementaryAssertion(idx); + } return idx; } @@ -2163,6 +2189,13 @@ void Compiler::optAssertionGen(GenTree* tree) assertionInfo = optAssertionGenJtrue(tree); break; + case GT_ASSERTION: + // The relop wrapped by GT_ASSERTION is known to be true at this point. + // Generate the same assertion we would for `JTRUE(relop)` taking the true edge, + // but skip the complementary assertion since this is a one-sided fact. + assertionInfo = optCreateJTrueBoundsAssertion(tree, /* createComplementary */ false); + break; + default: // All other gtOper node kinds, leave 'assertionIndex' = NO_ASSERTION_INDEX break; diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index e794aee7840842..6b69cd27bb99d0 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8726,7 +8726,7 @@ class Compiler // Assertion Gen functions. void optAssertionGen(GenTree* tree); AssertionIndex optAssertionGenCast(GenTreeCast* cast); - AssertionInfo optCreateJTrueBoundsAssertion(GenTree* tree); + AssertionInfo optCreateJTrueBoundsAssertion(GenTree* tree, bool createComplementary = true); AssertionInfo optAssertionGenJtrue(GenTree* tree); AssertionIndex optCreateJtrueAssertions(GenTree* op1, GenTree* op2, bool equals); AssertionIndex optFindComplementary(AssertionIndex assertionIndex); @@ -12490,6 +12490,7 @@ class GenTreeVisitor case GT_RUNTIMELOOKUP: case GT_ARR_ADDR: case GT_KEEPALIVE: + case GT_ASSERTION: case GT_INC_SATURATE: { GenTreeUnOp* const unOp = node->AsUnOp(); diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 0ff1ab40625d97..829caad36fa87d 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -4476,6 +4476,7 @@ GenTree::VisitResult GenTree::VisitOperandUses(TVisitor visitor) case GT_PUTARG_STK: case GT_RETURNTRAP: case GT_KEEPALIVE: + case GT_ASSERTION: case GT_INC_SATURATE: case GT_RETURN_SUSPEND: return visitor(&this->AsUnOp()->gtOp1); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 4745ab497f07ff..ab909716b8a932 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -6900,6 +6900,7 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse) case GT_BSWAP: case GT_BSWAP16: case GT_KEEPALIVE: + case GT_ASSERTION: case GT_INC_SATURATE: if (operand == this->AsUnOp()->gtOp1) { @@ -10547,6 +10548,7 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) case GT_BSWAP: case GT_BSWAP16: case GT_KEEPALIVE: + case GT_ASSERTION: case GT_INC_SATURATE: case GT_RETURNTRAP: case GT_RETURN_SUSPEND: diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index d14790d89fb622..22de526c339368 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -65,6 +65,7 @@ GTNODE(NEG , GenTreeOp ,0,0,GTK_UNOP) GTNODE(INTRINSIC , GenTreeIntrinsic ,0,0,GTK_BINOP|GTK_EXOP) GTNODE(KEEPALIVE , GenTree ,0,0,GTK_UNOP|GTK_NOVALUE) // keep operand alive, generate no code, produce no result +GTNODE(ASSERTION , GenTreeOp ,0,0,GTK_UNOP|GTK_NOVALUE|DBK_NOTLIR) // wraps a relop tree that is known to be true (used to seed assertions; removed by assertion prop) GTNODE(CAST , GenTreeCast ,0,0,GTK_UNOP|GTK_EXOP) // conversion to another type GTNODE(BITCAST , GenTreeOp ,0,1,GTK_UNOP) // reinterpretation of bits as another type diff --git a/src/coreclr/jit/inductionvariableopts.cpp b/src/coreclr/jit/inductionvariableopts.cpp index ae3df7acc20e69..18d839da2b302a 100644 --- a/src/coreclr/jit/inductionvariableopts.cpp +++ b/src/coreclr/jit/inductionvariableopts.cpp @@ -158,6 +158,12 @@ PerLoopInfo::LoopInfo* PerLoopInfo::GetOrCreateInfo(FlowGraphNaturalLoop* loop) for (Statement* stmt : block->NonPhiStatements()) { + // GT_ASSERTION is a marker that is dropped before LIR. Skip its uses + // here so they don't pin the IV and prevent strength reduction. + if (stmt->GetRootNode()->OperIs(GT_ASSERTION)) + { + continue; + } for (GenTree* node : stmt->TreeList()) { info.HasSuspensionPoint |= node->IsCall() && node->AsCall()->IsAsync(); diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index 6e1aca212fb5c1..c7095dd0af88c7 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -2567,6 +2567,7 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR case GT_IL_OFFSET: case GT_RECORD_ASYNC_RESUME: case GT_KEEPALIVE: + case GT_ASSERTION: case GT_SWIFT_ERROR_RET: case GT_GCPOLL: case GT_WASM_JEXCEPT: diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 636d588bb82d2a..66b291524b9fd2 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -2126,6 +2126,62 @@ bool Compiler::optTryInvertWhileLoop(FlowGraphNaturalLoop* loop) #endif // DEBUG Metrics.LoopsInverted++; + + // Synthesize a GT_ASSERTION at the start of `stayInLoopSucc` recording that the + // loop guard is known to be true on every entry into the loop body. This compensates + // for the cyclic value numbers that loop inversion creates on phi merges, which can + // otherwise prevent later phases (assertion prop, range check) from proving that + // the guard still holds inside the loop body. + { + GenTree* condRoot = condBlock->lastStmt()->GetRootNode(); + assert(condRoot->OperIs(GT_JTRUE)); + GenTree* origRelop = condRoot->gtGetOp1(); + + auto isInvariant = [](Compiler* comp, GenTree* node) -> bool { + if (!node->TypeIs(TYP_INT)) + return false; + if (node->OperIs(GT_LCL_VAR)) + { + unsigned lclNum = node->AsLclVar()->GetLclNum(); + return !comp->lvaTable[lclNum].IsAddressExposed(); + } + return node->IsInvariant(); + }; + + // Only seed the assertion for ordering relops on simple operands. EQ/NE + // (especially against masks) are not useful as loop-guard assertions and + // can interact poorly with later phases. + bool isSupportedRelop = origRelop->OperIs(GT_LT, GT_LE, GT_GT, GT_GE) && + isInvariant(this, origRelop->gtGetOp1()) && isInvariant(this, origRelop->gtGetOp2()); + + if (isSupportedRelop) + { + GenTree* relopClone = gtCloneExpr(origRelop); + if (trueExits) + { + relopClone = gtReverseCond(relopClone); + } + relopClone->gtFlags &= ~GTF_RELOP_JMP_USED; + // GT_ASSERTION is a pure marker: the relop is known true. Strip any + // effect flags carried over from the JTRUE clone so downstream phases + // (CSE, range check, block compaction, copy prop) don't treat it as + // having observable effects. + relopClone->gtFlags &= ~GTF_ALL_EFFECT; + GenTree* assertion = gtNewOperNode(GT_ASSERTION, TYP_VOID, relopClone); + Statement* stmt = fgNewStmtAtBeg(stayInLoopSucc, assertion, condBlock->lastStmt()->GetDebugInfo()); + if (fgNodeThreading == NodeThreading::AllTrees) + { + gtSetStmtInfo(stmt); + fgSetStmtSeq(stmt); + } + else if (fgNodeThreading == NodeThreading::AllLocals) + { + fgSequenceLocals(stmt); + } + JITDUMP("Inserted GT_ASSERTION at start of " FMT_BB " to record loop guard\n", stayInLoopSucc->bbNum); + } + } + return true; } diff --git a/src/coreclr/jit/rationalize.cpp b/src/coreclr/jit/rationalize.cpp index e32a1b13ef6b52..ce0f4c17e8f6fe 100644 --- a/src/coreclr/jit/rationalize.cpp +++ b/src/coreclr/jit/rationalize.cpp @@ -1924,6 +1924,14 @@ PhaseStatus Rationalizer::DoPhase() assert(statement->GetRootNode() != nullptr); assert(statement->GetRootNode()->gtNext == nullptr); + // Drop GT_ASSERTION statements; they are HIR-only seeds for assertion prop and + // should not reach LIR. Normally assertion prop removes them; this is a backstop + // for the case where assertion prop is disabled. + if (statement->GetRootNode()->OperIs(GT_ASSERTION)) + { + continue; + } + if (!statement->IsPhiDefnStmt()) // Note that we get rid of PHI nodes here. { BlockRange().InsertAtEnd(LIR::Range(statement->GetTreeList(), statement->GetRootNode())); diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 3d76fd6333b04f..4d74fbf728d5f5 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -12956,6 +12956,7 @@ void Compiler::fgValueNumberTree(GenTree* tree) case GT_RETFILT: case GT_RETURN_SUSPEND: case GT_NULLCHECK: + case GT_ASSERTION: if (tree->gtGetOp1() != nullptr) { tree->gtVNPair = vnStore->VNPWithExc(vnStore->VNPForVoid(), From 6cab5c148270f95dcd58069da02e34306227dfd8 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Tue, 21 Apr 2026 03:25:58 +0200 Subject: [PATCH 2/8] cleanup --- src/coreclr/jit/gtlist.h | 2 +- src/coreclr/jit/inductionvariableopts.cpp | 4 +- src/coreclr/jit/optimizer.cpp | 61 ++++++----------------- src/coreclr/jit/rationalize.cpp | 29 ++++++++--- 4 files changed, 41 insertions(+), 55 deletions(-) diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index 22de526c339368..d1f19f8f11be2b 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -65,7 +65,7 @@ GTNODE(NEG , GenTreeOp ,0,0,GTK_UNOP) GTNODE(INTRINSIC , GenTreeIntrinsic ,0,0,GTK_BINOP|GTK_EXOP) GTNODE(KEEPALIVE , GenTree ,0,0,GTK_UNOP|GTK_NOVALUE) // keep operand alive, generate no code, produce no result -GTNODE(ASSERTION , GenTreeOp ,0,0,GTK_UNOP|GTK_NOVALUE|DBK_NOTLIR) // wraps a relop tree that is known to be true (used to seed assertions; removed by assertion prop) +GTNODE(ASSERTION , GenTreeOp ,0,0,GTK_UNOP|GTK_NOVALUE|DBK_NOTLIR) // used to seed assertions GTNODE(CAST , GenTreeCast ,0,0,GTK_UNOP|GTK_EXOP) // conversion to another type GTNODE(BITCAST , GenTreeOp ,0,1,GTK_UNOP) // reinterpretation of bits as another type diff --git a/src/coreclr/jit/inductionvariableopts.cpp b/src/coreclr/jit/inductionvariableopts.cpp index 18d839da2b302a..b80327697a5947 100644 --- a/src/coreclr/jit/inductionvariableopts.cpp +++ b/src/coreclr/jit/inductionvariableopts.cpp @@ -160,10 +160,12 @@ PerLoopInfo::LoopInfo* PerLoopInfo::GetOrCreateInfo(FlowGraphNaturalLoop* loop) { // GT_ASSERTION is a marker that is dropped before LIR. Skip its uses // here so they don't pin the IV and prevent strength reduction. - if (stmt->GetRootNode()->OperIs(GT_ASSERTION)) + if (stmt->GetRootNode()->OperIs(GT_ASSERTION) && + ((stmt->GetRootNode()->gtGetOp1()->gtFlags & GTF_SIDE_EFFECT) == 0)) { continue; } + for (GenTree* node : stmt->TreeList()) { info.HasSuspensionPoint |= node->IsCall() && node->AsCall()->IsAsync(); diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 66b291524b9fd2..a4d47a8f01aaa7 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -2132,54 +2132,25 @@ bool Compiler::optTryInvertWhileLoop(FlowGraphNaturalLoop* loop) // for the cyclic value numbers that loop inversion creates on phi merges, which can // otherwise prevent later phases (assertion prop, range check) from proving that // the guard still holds inside the loop body. - { - GenTree* condRoot = condBlock->lastStmt()->GetRootNode(); - assert(condRoot->OperIs(GT_JTRUE)); - GenTree* origRelop = condRoot->gtGetOp1(); - - auto isInvariant = [](Compiler* comp, GenTree* node) -> bool { - if (!node->TypeIs(TYP_INT)) - return false; - if (node->OperIs(GT_LCL_VAR)) - { - unsigned lclNum = node->AsLclVar()->GetLclNum(); - return !comp->lvaTable[lclNum].IsAddressExposed(); - } - return node->IsInvariant(); - }; - - // Only seed the assertion for ordering relops on simple operands. EQ/NE - // (especially against masks) are not useful as loop-guard assertions and - // can interact poorly with later phases. - bool isSupportedRelop = origRelop->OperIs(GT_LT, GT_LE, GT_GT, GT_GE) && - isInvariant(this, origRelop->gtGetOp1()) && isInvariant(this, origRelop->gtGetOp2()); + // + auto isSuitableRelopOp = [this](GenTree* node) -> bool { + return node->IsInvariant() || (node->OperIs(GT_LCL_VAR) && !lvaVarAddrExposed(node->AsLclVar()->GetLclNum())); + }; - if (isSupportedRelop) + GenTree* origRelop = condBlock->lastStmt()->GetRootNode()->gtGetOp1(); + if (origRelop->OperIsCmpCompare() && isSuitableRelopOp(origRelop->gtGetOp1()) && + isSuitableRelopOp(origRelop->gtGetOp2())) + { + GenTree* relopClone = gtCloneExpr(origRelop); + if (trueExits) { - GenTree* relopClone = gtCloneExpr(origRelop); - if (trueExits) - { - relopClone = gtReverseCond(relopClone); - } - relopClone->gtFlags &= ~GTF_RELOP_JMP_USED; - // GT_ASSERTION is a pure marker: the relop is known true. Strip any - // effect flags carried over from the JTRUE clone so downstream phases - // (CSE, range check, block compaction, copy prop) don't treat it as - // having observable effects. - relopClone->gtFlags &= ~GTF_ALL_EFFECT; - GenTree* assertion = gtNewOperNode(GT_ASSERTION, TYP_VOID, relopClone); - Statement* stmt = fgNewStmtAtBeg(stayInLoopSucc, assertion, condBlock->lastStmt()->GetDebugInfo()); - if (fgNodeThreading == NodeThreading::AllTrees) - { - gtSetStmtInfo(stmt); - fgSetStmtSeq(stmt); - } - else if (fgNodeThreading == NodeThreading::AllLocals) - { - fgSequenceLocals(stmt); - } - JITDUMP("Inserted GT_ASSERTION at start of " FMT_BB " to record loop guard\n", stayInLoopSucc->bbNum); + relopClone = gtReverseCond(relopClone); } + relopClone->gtFlags &= ~GTF_RELOP_JMP_USED; + GenTree* assertion = gtNewOperNode(GT_ASSERTION, TYP_VOID, relopClone); + Statement* stmt = fgNewStmtFromTree(assertion, condBlock->lastStmt()->GetDebugInfo()); + fgInsertStmtAtBeg(stayInLoopSucc, stmt); + JITDUMP("Inserted GT_ASSERTION at start of " FMT_BB " to record loop guard\n", stayInLoopSucc->bbNum); } return true; diff --git a/src/coreclr/jit/rationalize.cpp b/src/coreclr/jit/rationalize.cpp index ce0f4c17e8f6fe..7e3caf8887a3b7 100644 --- a/src/coreclr/jit/rationalize.cpp +++ b/src/coreclr/jit/rationalize.cpp @@ -1726,6 +1726,27 @@ Compiler::fgWalkResult Rationalizer::RewriteNode(GenTree** useEdge, Compiler::Ge } break; + case GT_ASSERTION: + { + bool isClosed = false; + unsigned sideEffects = 0; + LIR::ReadOnlyRange range = BlockRange().GetTreeRange(node->gtGetOp1(), &isClosed, &sideEffects); + if (isClosed && ((sideEffects & GTF_SIDE_EFFECT) == 0)) + { + BlockRange().Delete(m_compiler, m_block, std::move(range)); + BlockRange().Remove(node); + return Compiler::WALK_CONTINUE; + } + + BlockRange().Remove(node); + node = node->gtGetOp1(); + if (node->IsValue()) + { + node->SetUnusedValue(); + } + break; + } + case GT_GCPOLL: { // GCPOLL is essentially a no-op, we used it as a hint for fgCreateGCPoll @@ -1924,14 +1945,6 @@ PhaseStatus Rationalizer::DoPhase() assert(statement->GetRootNode() != nullptr); assert(statement->GetRootNode()->gtNext == nullptr); - // Drop GT_ASSERTION statements; they are HIR-only seeds for assertion prop and - // should not reach LIR. Normally assertion prop removes them; this is a backstop - // for the case where assertion prop is disabled. - if (statement->GetRootNode()->OperIs(GT_ASSERTION)) - { - continue; - } - if (!statement->IsPhiDefnStmt()) // Note that we get rid of PHI nodes here. { BlockRange().InsertAtEnd(LIR::Range(statement->GetTreeList(), statement->GetRootNode())); From 5c9f174a42524ddfb8051592601d4068a6304d4a Mon Sep 17 00:00:00 2001 From: EgorBo Date: Tue, 21 Apr 2026 14:26:40 +0200 Subject: [PATCH 3/8] fb --- src/coreclr/jit/compiler.h | 11 ++++++++++ src/coreclr/jit/inductionvariableopts.cpp | 8 ------- src/coreclr/jit/optimizer.cpp | 1 + src/coreclr/jit/rangecheck.cpp | 26 ++++++++++++++++++++--- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 6b69cd27bb99d0..68ded9915479ee 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7615,6 +7615,7 @@ class Compiler #define OMF_HAS_STACK_ARRAY 0x00100000 // Method contains stack allocated arrays #define OMF_HAS_BOUNDS_CHECKS 0x00200000 // Method contains bounds checks #define OMF_HAS_EARLY_QMARKS 0x00400000 // Method contains early expandable QMARKs +#define OMF_HAS_ASSERTION_NODES 0x00800000 // Method contains GT_ASSERTION nodes // clang-format on @@ -7665,6 +7666,16 @@ class Compiler optMethodFlags |= OMF_HAS_EXPANDABLE_CAST; } + bool doesMethodHaveAssertionNodes() const + { + return (optMethodFlags & OMF_HAS_ASSERTION_NODES) != 0; + } + + void setMethodHasAssertionNodes() + { + optMethodFlags |= OMF_HAS_ASSERTION_NODES; + } + bool doesMethodHaveGuardedDevirtualization() const { return (optMethodFlags & OMF_HAS_GUARDEDDEVIRT) != 0; diff --git a/src/coreclr/jit/inductionvariableopts.cpp b/src/coreclr/jit/inductionvariableopts.cpp index b80327697a5947..ae3df7acc20e69 100644 --- a/src/coreclr/jit/inductionvariableopts.cpp +++ b/src/coreclr/jit/inductionvariableopts.cpp @@ -158,14 +158,6 @@ PerLoopInfo::LoopInfo* PerLoopInfo::GetOrCreateInfo(FlowGraphNaturalLoop* loop) for (Statement* stmt : block->NonPhiStatements()) { - // GT_ASSERTION is a marker that is dropped before LIR. Skip its uses - // here so they don't pin the IV and prevent strength reduction. - if (stmt->GetRootNode()->OperIs(GT_ASSERTION) && - ((stmt->GetRootNode()->gtGetOp1()->gtFlags & GTF_SIDE_EFFECT) == 0)) - { - continue; - } - for (GenTree* node : stmt->TreeList()) { info.HasSuspensionPoint |= node->IsCall() && node->AsCall()->IsAsync(); diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index a4d47a8f01aaa7..cbee2131be1d04 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -2150,6 +2150,7 @@ bool Compiler::optTryInvertWhileLoop(FlowGraphNaturalLoop* loop) GenTree* assertion = gtNewOperNode(GT_ASSERTION, TYP_VOID, relopClone); Statement* stmt = fgNewStmtFromTree(assertion, condBlock->lastStmt()->GetDebugInfo()); fgInsertStmtAtBeg(stayInLoopSucc, stmt); + setMethodHasAssertionNodes(); JITDUMP("Inserted GT_ASSERTION at start of " FMT_BB " to record loop guard\n", stayInLoopSucc->bbNum); } diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index dca63fb6efdb2a..e0c6bf93c7056a 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -14,12 +14,32 @@ // PhaseStatus Compiler::rangeCheckPhase() { - if (!doesMethodHaveBoundsChecks() || (fgSsaPassesCompleted == 0)) + bool madeChanges = false; + if (doesMethodHaveBoundsChecks() && (fgSsaPassesCompleted > 0)) { - return PhaseStatus::MODIFIED_NOTHING; + madeChanges = GetRangeCheck()->OptimizeRangeChecks(); + } + + // GT_ASSERTION nodes are no longer needed. Remove all side-effect-free instances + // to avoid pessimizing subsequent phases + if (doesMethodHaveAssertionNodes()) + { + for (BasicBlock* const block : Blocks()) + { + for (Statement* stmt = block->firstStmt(); stmt != nullptr;) + { + Statement* nextStmt = stmt->GetNextStmt(); + GenTree* root = stmt->GetRootNode(); + if (root->OperIs(GT_ASSERTION) && ((root->gtGetOp1()->gtFlags & GTF_SIDE_EFFECT) == 0)) + { + fgRemoveStmt(block, stmt); + madeChanges = true; + } + stmt = nextStmt; + } + } } - const bool madeChanges = GetRangeCheck()->OptimizeRangeChecks(); return madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING; } From fac13d522cb4b530e86993262f53f27d339e4163 Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Tue, 21 Apr 2026 14:28:16 +0200 Subject: [PATCH 4/8] Update src/coreclr/jit/gtlist.h Co-authored-by: Jakob Botsch Nielsen --- src/coreclr/jit/gtlist.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index d1f19f8f11be2b..04d5789ea16798 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -65,7 +65,7 @@ GTNODE(NEG , GenTreeOp ,0,0,GTK_UNOP) GTNODE(INTRINSIC , GenTreeIntrinsic ,0,0,GTK_BINOP|GTK_EXOP) GTNODE(KEEPALIVE , GenTree ,0,0,GTK_UNOP|GTK_NOVALUE) // keep operand alive, generate no code, produce no result -GTNODE(ASSERTION , GenTreeOp ,0,0,GTK_UNOP|GTK_NOVALUE|DBK_NOTLIR) // used to seed assertions +GTNODE(ASSERTION , GenTreeOp ,0,0,GTK_UNOP|GTK_NOVALUE|DBK_NOTLIR) // op1 is non-zero GTNODE(CAST , GenTreeCast ,0,0,GTK_UNOP|GTK_EXOP) // conversion to another type GTNODE(BITCAST , GenTreeOp ,0,1,GTK_UNOP) // reinterpretation of bits as another type From 5db9d641def504d3de0dcaf8cf0b7b7bbd8e597d Mon Sep 17 00:00:00 2001 From: EgorBo Date: Tue, 21 Apr 2026 14:45:20 +0200 Subject: [PATCH 5/8] fb --- src/coreclr/jit/optimizer.cpp | 28 ++++++++++++++++------------ src/coreclr/jit/rationalize.cpp | 22 +--------------------- 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index cbee2131be1d04..31493896feccae 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -2137,21 +2137,25 @@ bool Compiler::optTryInvertWhileLoop(FlowGraphNaturalLoop* loop) return node->IsInvariant() || (node->OperIs(GT_LCL_VAR) && !lvaVarAddrExposed(node->AsLclVar()->GetLclNum())); }; - GenTree* origRelop = condBlock->lastStmt()->GetRootNode()->gtGetOp1(); - if (origRelop->OperIsCmpCompare() && isSuitableRelopOp(origRelop->gtGetOp1()) && - isSuitableRelopOp(origRelop->gtGetOp2())) + GenTree* jtrue = condBlock->lastStmt()->GetRootNode(); + if (jtrue->OperIs(GT_JTRUE)) { - GenTree* relopClone = gtCloneExpr(origRelop); - if (trueExits) + GenTree* origRelop = condBlock->lastStmt()->GetRootNode()->gtGetOp1(); + if (origRelop->OperIsCmpCompare() && isSuitableRelopOp(origRelop->gtGetOp1()) && + isSuitableRelopOp(origRelop->gtGetOp2())) { - relopClone = gtReverseCond(relopClone); + GenTree* relopClone = gtCloneExpr(origRelop); + if (trueExits) + { + relopClone = gtReverseCond(relopClone); + } + relopClone->gtFlags &= ~GTF_RELOP_JMP_USED; + GenTree* assertion = gtNewOperNode(GT_ASSERTION, TYP_VOID, relopClone); + Statement* stmt = fgNewStmtFromTree(assertion, condBlock->lastStmt()->GetDebugInfo()); + fgInsertStmtAtBeg(stayInLoopSucc, stmt); + setMethodHasAssertionNodes(); + JITDUMP("Inserted GT_ASSERTION at start of " FMT_BB " to record loop guard\n", stayInLoopSucc->bbNum); } - relopClone->gtFlags &= ~GTF_RELOP_JMP_USED; - GenTree* assertion = gtNewOperNode(GT_ASSERTION, TYP_VOID, relopClone); - Statement* stmt = fgNewStmtFromTree(assertion, condBlock->lastStmt()->GetDebugInfo()); - fgInsertStmtAtBeg(stayInLoopSucc, stmt); - setMethodHasAssertionNodes(); - JITDUMP("Inserted GT_ASSERTION at start of " FMT_BB " to record loop guard\n", stayInLoopSucc->bbNum); } return true; diff --git a/src/coreclr/jit/rationalize.cpp b/src/coreclr/jit/rationalize.cpp index 7e3caf8887a3b7..67c0a2fcd841aa 100644 --- a/src/coreclr/jit/rationalize.cpp +++ b/src/coreclr/jit/rationalize.cpp @@ -1716,6 +1716,7 @@ Compiler::fgWalkResult Rationalizer::RewriteNode(GenTree** useEdge, Compiler::Ge case GT_BOX: case GT_ARR_ADDR: + case GT_ASSERTION: // BOX/ARR_ADDR are "passthrough" nodes, // and at this point we no longer need them. if (node->gtGetOp1() != nullptr) @@ -1726,27 +1727,6 @@ Compiler::fgWalkResult Rationalizer::RewriteNode(GenTree** useEdge, Compiler::Ge } break; - case GT_ASSERTION: - { - bool isClosed = false; - unsigned sideEffects = 0; - LIR::ReadOnlyRange range = BlockRange().GetTreeRange(node->gtGetOp1(), &isClosed, &sideEffects); - if (isClosed && ((sideEffects & GTF_SIDE_EFFECT) == 0)) - { - BlockRange().Delete(m_compiler, m_block, std::move(range)); - BlockRange().Remove(node); - return Compiler::WALK_CONTINUE; - } - - BlockRange().Remove(node); - node = node->gtGetOp1(); - if (node->IsValue()) - { - node->SetUnusedValue(); - } - break; - } - case GT_GCPOLL: { // GCPOLL is essentially a no-op, we used it as a hint for fgCreateGCPoll From a44a7913f8b81140ed12830fa89475af2d43b2ea Mon Sep 17 00:00:00 2001 From: EgorBo Date: Tue, 21 Apr 2026 14:48:31 +0200 Subject: [PATCH 6/8] mark as DONT_CSE --- src/coreclr/jit/optimizer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 31493896feccae..7da9e36b2908c5 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -2150,6 +2150,8 @@ bool Compiler::optTryInvertWhileLoop(FlowGraphNaturalLoop* loop) relopClone = gtReverseCond(relopClone); } relopClone->gtFlags &= ~GTF_RELOP_JMP_USED; + relopClone->gtFlags |= GTF_DONT_CSE; + GenTree* assertion = gtNewOperNode(GT_ASSERTION, TYP_VOID, relopClone); Statement* stmt = fgNewStmtFromTree(assertion, condBlock->lastStmt()->GetDebugInfo()); fgInsertStmtAtBeg(stayInLoopSucc, stmt); From 6cef78f4353c99c0b0ce25f691f9d1adb3208111 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Tue, 21 Apr 2026 14:57:32 +0200 Subject: [PATCH 7/8] cleanup --- src/coreclr/jit/rationalize.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/rationalize.cpp b/src/coreclr/jit/rationalize.cpp index 67c0a2fcd841aa..3d2f1a62f2165b 100644 --- a/src/coreclr/jit/rationalize.cpp +++ b/src/coreclr/jit/rationalize.cpp @@ -1717,7 +1717,7 @@ Compiler::fgWalkResult Rationalizer::RewriteNode(GenTree** useEdge, Compiler::Ge case GT_BOX: case GT_ARR_ADDR: case GT_ASSERTION: - // BOX/ARR_ADDR are "passthrough" nodes, + // BOX/ARR_ADDR/ASSERTION are "passthrough" nodes, // and at this point we no longer need them. if (node->gtGetOp1() != nullptr) { From a30983e922299bbc6f5fc19a8f5a66d54181bfdc Mon Sep 17 00:00:00 2001 From: EgorBo Date: Tue, 21 Apr 2026 15:28:17 +0200 Subject: [PATCH 8/8] rename to ASSUME --- src/coreclr/jit/assertionprop.cpp | 4 ++-- src/coreclr/jit/compiler.h | 12 ++++++------ src/coreclr/jit/compiler.hpp | 2 +- src/coreclr/jit/gentree.cpp | 4 ++-- src/coreclr/jit/gtlist.h | 2 +- src/coreclr/jit/liveness.cpp | 2 +- src/coreclr/jit/optimizer.cpp | 8 ++++---- src/coreclr/jit/rangecheck.cpp | 6 +++--- src/coreclr/jit/rationalize.cpp | 2 +- src/coreclr/jit/valuenum.cpp | 2 +- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 69da3587bfd56c..5d299d10ede2f9 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -2189,8 +2189,8 @@ void Compiler::optAssertionGen(GenTree* tree) assertionInfo = optAssertionGenJtrue(tree); break; - case GT_ASSERTION: - // The relop wrapped by GT_ASSERTION is known to be true at this point. + case GT_ASSUME: + // The relop wrapped by GT_ASSUME is known to be true at this point. // Generate the same assertion we would for `JTRUE(relop)` taking the true edge, // but skip the complementary assertion since this is a one-sided fact. assertionInfo = optCreateJTrueBoundsAssertion(tree, /* createComplementary */ false); diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 68ded9915479ee..f753149cb3e2bd 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7615,7 +7615,7 @@ class Compiler #define OMF_HAS_STACK_ARRAY 0x00100000 // Method contains stack allocated arrays #define OMF_HAS_BOUNDS_CHECKS 0x00200000 // Method contains bounds checks #define OMF_HAS_EARLY_QMARKS 0x00400000 // Method contains early expandable QMARKs -#define OMF_HAS_ASSERTION_NODES 0x00800000 // Method contains GT_ASSERTION nodes +#define OMF_HAS_ASSUMPTIONS 0x00800000 // Method contains GT_ASSUME nodes // clang-format on @@ -7666,14 +7666,14 @@ class Compiler optMethodFlags |= OMF_HAS_EXPANDABLE_CAST; } - bool doesMethodHaveAssertionNodes() const + bool doesMethodHaveAssumptions() const { - return (optMethodFlags & OMF_HAS_ASSERTION_NODES) != 0; + return (optMethodFlags & OMF_HAS_ASSUMPTIONS) != 0; } - void setMethodHasAssertionNodes() + void setMethodHasAssumptions() { - optMethodFlags |= OMF_HAS_ASSERTION_NODES; + optMethodFlags |= OMF_HAS_ASSUMPTIONS; } bool doesMethodHaveGuardedDevirtualization() const @@ -12501,7 +12501,7 @@ class GenTreeVisitor case GT_RUNTIMELOOKUP: case GT_ARR_ADDR: case GT_KEEPALIVE: - case GT_ASSERTION: + case GT_ASSUME: case GT_INC_SATURATE: { GenTreeUnOp* const unOp = node->AsUnOp(); diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 829caad36fa87d..a5e31a6cef356d 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -4476,7 +4476,7 @@ GenTree::VisitResult GenTree::VisitOperandUses(TVisitor visitor) case GT_PUTARG_STK: case GT_RETURNTRAP: case GT_KEEPALIVE: - case GT_ASSERTION: + case GT_ASSUME: case GT_INC_SATURATE: case GT_RETURN_SUSPEND: return visitor(&this->AsUnOp()->gtOp1); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index ab909716b8a932..49d37246701a23 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -6900,7 +6900,7 @@ bool GenTree::TryGetUse(GenTree* operand, GenTree*** pUse) case GT_BSWAP: case GT_BSWAP16: case GT_KEEPALIVE: - case GT_ASSERTION: + case GT_ASSUME: case GT_INC_SATURATE: if (operand == this->AsUnOp()->gtOp1) { @@ -10548,7 +10548,7 @@ GenTreeUseEdgeIterator::GenTreeUseEdgeIterator(GenTree* node) case GT_BSWAP: case GT_BSWAP16: case GT_KEEPALIVE: - case GT_ASSERTION: + case GT_ASSUME: case GT_INC_SATURATE: case GT_RETURNTRAP: case GT_RETURN_SUSPEND: diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index 04d5789ea16798..9672fed5bd93f8 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -65,7 +65,7 @@ GTNODE(NEG , GenTreeOp ,0,0,GTK_UNOP) GTNODE(INTRINSIC , GenTreeIntrinsic ,0,0,GTK_BINOP|GTK_EXOP) GTNODE(KEEPALIVE , GenTree ,0,0,GTK_UNOP|GTK_NOVALUE) // keep operand alive, generate no code, produce no result -GTNODE(ASSERTION , GenTreeOp ,0,0,GTK_UNOP|GTK_NOVALUE|DBK_NOTLIR) // op1 is non-zero +GTNODE(ASSUME , GenTreeOp ,0,0,GTK_UNOP|GTK_NOVALUE|DBK_NOTLIR) // op1 is non-zero GTNODE(CAST , GenTreeCast ,0,0,GTK_UNOP|GTK_EXOP) // conversion to another type GTNODE(BITCAST , GenTreeOp ,0,1,GTK_UNOP) // reinterpretation of bits as another type diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index c7095dd0af88c7..774b67c6835958 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -2567,7 +2567,7 @@ void Liveness::ComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VAR case GT_IL_OFFSET: case GT_RECORD_ASYNC_RESUME: case GT_KEEPALIVE: - case GT_ASSERTION: + case GT_ASSUME: case GT_SWIFT_ERROR_RET: case GT_GCPOLL: case GT_WASM_JEXCEPT: diff --git a/src/coreclr/jit/optimizer.cpp b/src/coreclr/jit/optimizer.cpp index 7da9e36b2908c5..cefa0bf1708237 100644 --- a/src/coreclr/jit/optimizer.cpp +++ b/src/coreclr/jit/optimizer.cpp @@ -2127,7 +2127,7 @@ bool Compiler::optTryInvertWhileLoop(FlowGraphNaturalLoop* loop) Metrics.LoopsInverted++; - // Synthesize a GT_ASSERTION at the start of `stayInLoopSucc` recording that the + // Synthesize a GT_ASSUME at the start of `stayInLoopSucc` recording that the // loop guard is known to be true on every entry into the loop body. This compensates // for the cyclic value numbers that loop inversion creates on phi merges, which can // otherwise prevent later phases (assertion prop, range check) from proving that @@ -2152,11 +2152,11 @@ bool Compiler::optTryInvertWhileLoop(FlowGraphNaturalLoop* loop) relopClone->gtFlags &= ~GTF_RELOP_JMP_USED; relopClone->gtFlags |= GTF_DONT_CSE; - GenTree* assertion = gtNewOperNode(GT_ASSERTION, TYP_VOID, relopClone); + GenTree* assertion = gtNewOperNode(GT_ASSUME, TYP_VOID, relopClone); Statement* stmt = fgNewStmtFromTree(assertion, condBlock->lastStmt()->GetDebugInfo()); fgInsertStmtAtBeg(stayInLoopSucc, stmt); - setMethodHasAssertionNodes(); - JITDUMP("Inserted GT_ASSERTION at start of " FMT_BB " to record loop guard\n", stayInLoopSucc->bbNum); + setMethodHasAssumptions(); + JITDUMP("Inserted GT_ASSUME at start of " FMT_BB " to record loop guard\n", stayInLoopSucc->bbNum); } } diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index e0c6bf93c7056a..5f61ccdcddf1a5 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -20,9 +20,9 @@ PhaseStatus Compiler::rangeCheckPhase() madeChanges = GetRangeCheck()->OptimizeRangeChecks(); } - // GT_ASSERTION nodes are no longer needed. Remove all side-effect-free instances + // GT_ASSUME nodes are no longer needed. Remove all side-effect-free instances // to avoid pessimizing subsequent phases - if (doesMethodHaveAssertionNodes()) + if (doesMethodHaveAssumptions()) { for (BasicBlock* const block : Blocks()) { @@ -30,7 +30,7 @@ PhaseStatus Compiler::rangeCheckPhase() { Statement* nextStmt = stmt->GetNextStmt(); GenTree* root = stmt->GetRootNode(); - if (root->OperIs(GT_ASSERTION) && ((root->gtGetOp1()->gtFlags & GTF_SIDE_EFFECT) == 0)) + if (root->OperIs(GT_ASSUME) && ((root->gtGetOp1()->gtFlags & GTF_SIDE_EFFECT) == 0)) { fgRemoveStmt(block, stmt); madeChanges = true; diff --git a/src/coreclr/jit/rationalize.cpp b/src/coreclr/jit/rationalize.cpp index 3d2f1a62f2165b..4e897447b72bdf 100644 --- a/src/coreclr/jit/rationalize.cpp +++ b/src/coreclr/jit/rationalize.cpp @@ -1716,7 +1716,7 @@ Compiler::fgWalkResult Rationalizer::RewriteNode(GenTree** useEdge, Compiler::Ge case GT_BOX: case GT_ARR_ADDR: - case GT_ASSERTION: + case GT_ASSUME: // BOX/ARR_ADDR/ASSERTION are "passthrough" nodes, // and at this point we no longer need them. if (node->gtGetOp1() != nullptr) diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 4d74fbf728d5f5..01d811cf5ac315 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -12956,7 +12956,7 @@ void Compiler::fgValueNumberTree(GenTree* tree) case GT_RETFILT: case GT_RETURN_SUSPEND: case GT_NULLCHECK: - case GT_ASSERTION: + case GT_ASSUME: if (tree->gtGetOp1() != nullptr) { tree->gtVNPair = vnStore->VNPWithExc(vnStore->VNPForVoid(),