From a3e7c1732e70222109968fe3fc1d3498b405e5ec Mon Sep 17 00:00:00 2001 From: EgorBo Date: Thu, 10 Jul 2025 17:05:42 +0200 Subject: [PATCH 1/2] Fold ARR_LENGTH to CNS with live assertions --- src/coreclr/jit/assertionprop.cpp | 76 ++++++++++++++++++++++++++----- src/coreclr/jit/compiler.h | 3 +- src/coreclr/jit/gentree.cpp | 3 ++ 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index b63ea4084e11d8..2cc274be526085 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -3566,16 +3566,14 @@ GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeL // of kill sets. We will still make a == b copy assertions during the global phase to allow // for any implied assertions that can be retrieved. Because implied assertions look for // matching SSA numbers (i.e., if a0 == b1 and b1 == c0 then a0 == c0) they don't need kill sets. - if (optLocalAssertionProp) + assert(optLocalAssertionProp); + + // Perform copy assertion prop. + GenTree* newTree = optCopyAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex)); + if (newTree != nullptr) { - // Perform copy assertion prop. - GenTree* newTree = optCopyAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex)); - if (newTree != nullptr) - { - return newTree; - } + return newTree; } - continue; } @@ -4865,6 +4863,58 @@ GenTree* Compiler::optAssertionProp_Comma(ASSERT_VALARG_TP assertions, GenTree* return nullptr; } +//------------------------------------------------------------------------ +// optAssertionProp_ArrMetaData: see if we can optimize anything for ArrMetaData +// using live assertions. +// +// Arguments: +// assertions - set of live assertions +// tree - ArrMetaData tree to possibly optimize +// stmt - statement containing the tree +// +// Returns: +// The modified tree, or nullptr if no assertion prop took place. +// +GenTree* Compiler::optAssertionProp_ArrMetaData(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt) +{ + assert(GenTree::OperIsArrMetaData(tree->OperGet())); + + GenTree* const addr = tree->GetIndirOrArrMetaDataAddr(); + ValueNum const vn = optConservativeNormalVN(tree); + + if (vn == ValueNumStore::NoVN) + { + return nullptr; + } + + assert(!optLocalAssertionProp); + + // if we have an arr.Length with " == CNS" assertion, replace it with that CNS + // if the expression has no side-effects. + // + // NOTE: Currently, we don't remove GTF_EXCEPT from ArrMetaData trees if we can prove + // the arrayObj is never null, see https://github.com/dotnet/runtime/pull/93531 + const bool hasNoSideEffects = + addr->OperIs(GT_LCL_VAR) && (((tree->gtFlags & GTF_EXCEPT) == 0) || optAssertionIsNonNull(addr, assertions)); + + if (hasNoSideEffects) + { + BitVecOps::Iter iter(apTraits, assertions); + unsigned index = 0; + while (iter.NextElem(&index)) + { + AssertionDsc* curAssertion = optGetAssertion(GetAssertionIndex(index)); + if (curAssertion->IsConstantInt32Assertion() && (curAssertion->op1.vn == vn)) + { + return optAssertionProp_Update(gtNewIconNodeWithVN(this, curAssertion->op2.u1.iconVal, tree->TypeGet()), + tree, stmt); + } + } + } + + return nullptr; +} + //------------------------------------------------------------------------ // optAssertionProp_Ind: see if we can prove the indirection can't cause // and exception. @@ -4887,7 +4937,7 @@ GenTree* Compiler::optAssertionProp_Ind(ASSERT_VALARG_TP assertions, GenTree* tr bool updated = optNonNullAssertionProp_Ind(assertions, tree); if (tree->OperIs(GT_STOREIND)) { - updated |= optWriteBarrierAssertionProp_StoreInd(assertions, tree->AsStoreInd()); + updated |= optWriteBarrierAssertionProp_StoreInd(tree->AsStoreInd()); } if (updated) @@ -5170,13 +5220,12 @@ static GCInfo::WriteBarrierForm GetWriteBarrierForm(Compiler* comp, ValueNum vn) // * Target is definitely on the heap - checked (slower) write barrier is not required // // Arguments: -// assertions - Active assertions // indir - The STOREIND node // // Return Value: // Whether the exact type of write barrier was determined and marked on the STOREIND node. // -bool Compiler::optWriteBarrierAssertionProp_StoreInd(ASSERT_VALARG_TP assertions, GenTreeStoreInd* indir) +bool Compiler::optWriteBarrierAssertionProp_StoreInd(GenTreeStoreInd* indir) { const GenTree* value = indir->AsIndir()->Data(); const GenTree* addr = indir->AsIndir()->Addr(); @@ -5552,6 +5601,11 @@ GenTree* Compiler::optAssertionProp(ASSERT_VALARG_TP assertions, GenTree* tree, case GT_NULLCHECK: return optAssertionProp_Ind(assertions, tree, stmt); + case GT_ARR_LENGTH: + case GT_MDARR_LENGTH: + case GT_MDARR_LOWER_BOUND: + return optAssertionProp_ArrMetaData(assertions, tree, stmt); + case GT_BOUNDS_CHECK: return optAssertionProp_BndsChk(assertions, tree, stmt); diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index ae47054b200afc..22d868e2155d36 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8095,6 +8095,7 @@ class Compiler GenTree* optAssertionProp_ModDiv(ASSERT_VALARG_TP assertions, GenTreeOp* tree, Statement* stmt, BasicBlock* block); GenTree* optAssertionProp_Return(ASSERT_VALARG_TP assertions, GenTreeOp* ret, Statement* stmt); GenTree* optAssertionProp_Ind(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); + GenTree* optAssertionProp_ArrMetaData(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); GenTree* optAssertionProp_Cast(ASSERT_VALARG_TP assertions, GenTreeCast* cast, Statement* stmt, BasicBlock* block); GenTree* optAssertionProp_Call(ASSERT_VALARG_TP assertions, GenTreeCall* call, Statement* stmt); GenTree* optAssertionProp_RelOp(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt, BasicBlock* block); @@ -8108,7 +8109,7 @@ class Compiler GenTree* optAssertionProp_Update(GenTree* newTree, GenTree* tree, Statement* stmt); GenTree* optNonNullAssertionProp_Call(ASSERT_VALARG_TP assertions, GenTreeCall* call); bool optNonNullAssertionProp_Ind(ASSERT_VALARG_TP assertions, GenTree* indir); - bool optWriteBarrierAssertionProp_StoreInd(ASSERT_VALARG_TP assertions, GenTreeStoreInd* indir); + bool optWriteBarrierAssertionProp_StoreInd(GenTreeStoreInd* indir); enum class AssertVisit { diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index b14f8ecf1b6787..5859b8b67e524f 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -7335,6 +7335,9 @@ bool GenTree::OperSupportsOrderingSideEffect() const { case GT_ARR_ADDR: case GT_BOUNDS_CHECK: + case GT_ARR_LENGTH: + case GT_MDARR_LENGTH: + case GT_MDARR_LOWER_BOUND: case GT_IND: case GT_BLK: case GT_STOREIND: From 1d59de3c30eeb0baba459694639e1b785f3ab5d9 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Thu, 10 Jul 2025 21:07:49 +0200 Subject: [PATCH 2/2] fix CI --- src/coreclr/jit/assertionprop.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 2cc274be526085..0c2561f5baab1f 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -4904,8 +4904,10 @@ GenTree* Compiler::optAssertionProp_ArrMetaData(ASSERT_VALARG_TP assertions, Gen while (iter.NextElem(&index)) { AssertionDsc* curAssertion = optGetAssertion(GetAssertionIndex(index)); - if (curAssertion->IsConstantInt32Assertion() && (curAssertion->op1.vn == vn)) + if (curAssertion->IsConstantInt32Assertion() && (curAssertion->op1.vn == vn) && + (curAssertion->assertionKind == OAK_EQUAL)) { + assert(!curAssertion->op2.HasIconFlag()); return optAssertionProp_Update(gtNewIconNodeWithVN(this, curAssertion->op2.u1.iconVal, tree->TypeGet()), tree, stmt); }