From d25a63a5effa905e09c018db976cb224c9d2b135 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 31 Jan 2026 18:36:58 +0100 Subject: [PATCH 01/17] Refactoring --- src/coreclr/jit/assertionprop.cpp | 152 +++++++-------------------- src/coreclr/jit/compiler.h | 169 ++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 113 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index a0d97cb1589f0f..999ddc1efb6f58 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1147,11 +1147,14 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser if (op1->OperIs(GT_BOUNDS_CHECK) && (assertionKind == OAK_NO_THROW)) { - GenTreeBoundsChk* arrBndsChk = op1->AsBoundsChk(); - assertion.assertionKind = assertionKind; - assertion.op1.kind = O1K_ARR_BND; - assertion.op1.bnd.vnIdx = optConservativeNormalVN(arrBndsChk->GetIndex()); - assertion.op1.bnd.vnLen = optConservativeNormalVN(arrBndsChk->GetArrayLength()); + ValueNum idxVN = optConservativeNormalVN(op1->AsBoundsChk()->GetIndex()); + ValueNum lenVN = optConservativeNormalVN(op1->AsBoundsChk()->GetArrayLength()); + if ((idxVN == ValueNumStore::NoVN) || (lenVN == ValueNumStore::NoVN)) + { + return NO_ASSERTION_INDEX; + } + assertion = AssertionDsc::CreateNoThrowArrBnd(this, idxVN, lenVN); + return optFinalizeCreatingAssertion(&assertion); } // // Are we trying to make a non-null assertion? @@ -1186,14 +1189,14 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser if (!fgIsBigOffset(offset) && op1->OperIs(GT_LCL_VAR) && !lvaVarAddrExposed(op1->AsLclVar()->GetLclNum())) { - assertion.op1.kind = O1K_LCLVAR; - assertion.op1.lclNum = op1->AsLclVarCommon()->GetLclNum(); - assertion.op1.vn = optConservativeNormalVN(op1); - assertion.assertionKind = assertionKind; - assertion.op2.kind = O2K_CONST_INT; - assertion.op2.vn = ValueNumStore::VNForNull(); - assertion.op2.u1.iconVal = 0; - assertion.op2.SetIconFlag(GTF_EMPTY); + ValueNum op1VN = optConservativeNormalVN(op1); + unsigned lclNum = op1->AsLclVar()->GetLclNum(); + if ((!optLocalAssertionProp && op1VN == ValueNumStore::NoVN) || + (optLocalAssertionProp && lclNum == BAD_VAR_NUM)) + { + return NO_ASSERTION_INDEX; + } + assertion = AssertionDsc::CreateNonNullAssertion(this, lclNum, op1VN); } } // @@ -1413,16 +1416,12 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser ValueNum op2VN = optConservativeNormalVN(op2); // For TP reasons, limited to 32-bit constants on the op2 side. - if (vnStore->IsVNInt32Constant(op2VN) && !vnStore->IsVNHandle(op2VN)) + if (op1VN != ValueNumStore::NoVN && op2VN != ValueNumStore::NoVN && vnStore->IsVNInt32Constant(op2VN) && + !vnStore->IsVNHandle(op2VN)) { assert(assertionKind == OAK_EQUAL || assertionKind == OAK_NOT_EQUAL); - assertion.assertionKind = assertionKind; - assertion.op1.vn = op1VN; - assertion.op1.kind = O1K_VN; - assertion.op2.vn = op2VN; - assertion.op2.kind = O2K_CONST_INT; - assertion.op2.u1.iconVal = vnStore->ConstantValue(op2VN); - assertion.op2.SetIconFlag(GTF_EMPTY); + assertion = + AssertionDsc::CreateInt32ConstantVNAssertion(this, op1VN, op2VN, assertionKind == OAK_EQUAL); return optAddAssertion(&assertion); } } @@ -1909,7 +1908,6 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) { return NO_ASSERTION_INDEX; } - GenTree* op2 = relop->gtGetOp2(); ValueNum relopVN = vnStore->VNConservativeNormalValue(relop->gtVNPair); ValueNumStore::UnsignedCompareCheckedBoundInfo unsignedCompareBnd; @@ -1919,14 +1917,7 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "i < bnd +/- k != 0" if (vnStore->IsVNCompareCheckedBoundArith(relopVN)) { - AssertionDsc dsc; - dsc.assertionKind = OAK_NOT_EQUAL; - dsc.op1.kind = O1K_BOUND_OPER_BND; - dsc.op1.vn = relopVN; - dsc.op2.kind = O2K_CONST_INT; - dsc.op2.vn = vnStore->VNZeroForType(op2->TypeGet()); - dsc.op2.u1.iconVal = 0; - dsc.op2.SetIconFlag(GTF_EMPTY); + AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBoundArith(this, relopVN); AssertionIndex index = optAddAssertion(&dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; @@ -1936,14 +1927,7 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "i < bnd != 0" else if (vnStore->IsVNCompareCheckedBound(relopVN)) { - AssertionDsc dsc; - dsc.assertionKind = OAK_NOT_EQUAL; - dsc.op1.kind = O1K_BOUND_LOOP_BND; - dsc.op1.vn = relopVN; - dsc.op2.kind = O2K_CONST_INT; - dsc.op2.vn = vnStore->VNZeroForType(TYP_INT); - dsc.op2.u1.iconVal = 0; - dsc.op2.SetIconFlag(GTF_EMPTY); + AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBound(this, relopVN); AssertionIndex index = optAddAssertion(&dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; @@ -1952,25 +1936,7 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "no throw" since this condition guarantees that i is both >= 0 and < bnd (on the appropriate edge) else if (vnStore->IsVNUnsignedCompareCheckedBound(relopVN, &unsignedCompareBnd)) { - assert(unsignedCompareBnd.vnIdx != ValueNumStore::NoVN); - assert((unsignedCompareBnd.cmpOper == VNF_LT_UN) || (unsignedCompareBnd.cmpOper == VNF_GE_UN)); - assert(vnStore->IsVNCheckedBound(unsignedCompareBnd.vnBound)); - - AssertionDsc dsc; - dsc.assertionKind = OAK_NO_THROW; - dsc.op1.kind = O1K_ARR_BND; - dsc.op1.vn = relopVN; - dsc.op1.bnd.vnIdx = unsignedCompareBnd.vnIdx; - dsc.op1.bnd.vnLen = vnStore->VNNormalValue(unsignedCompareBnd.vnBound); - dsc.op2.kind = O2K_INVALID; - dsc.op2.vn = ValueNumStore::NoVN; - - if ((dsc.op1.bnd.vnIdx == ValueNumStore::NoVN) || (dsc.op1.bnd.vnLen == ValueNumStore::NoVN)) - { - // Don't make an assertion if one of the operands has no VN - return NO_ASSERTION_INDEX; - } - + AssertionDsc dsc = AssertionDsc::CreateUnsignedCompareCheckedBound(this, relopVN, unsignedCompareBnd); AssertionIndex index = optAddAssertion(&dsc); if (unsignedCompareBnd.cmpOper == VNF_GE_UN) { @@ -1985,28 +1951,14 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "i < 100 != 0" else if (vnStore->IsVNConstantBound(relopVN)) { - AssertionDsc dsc; - dsc.assertionKind = OAK_NOT_EQUAL; - dsc.op1.kind = O1K_CONSTANT_LOOP_BND; - dsc.op1.vn = relopVN; - dsc.op2.kind = O2K_CONST_INT; - dsc.op2.vn = vnStore->VNZeroForType(TYP_INT); - dsc.op2.u1.iconVal = 0; - dsc.op2.SetIconFlag(GTF_EMPTY); + AssertionDsc dsc = AssertionDsc::CreateConstantBound(this, relopVN); AssertionIndex index = optAddAssertion(&dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; } else if (vnStore->IsVNConstantBoundUnsigned(relopVN)) { - AssertionDsc dsc; - dsc.assertionKind = OAK_NOT_EQUAL; - dsc.op1.kind = O1K_CONSTANT_LOOP_BND_UN; - dsc.op1.vn = relopVN; - dsc.op2.kind = O2K_CONST_INT; - dsc.op2.vn = vnStore->VNZeroForType(TYP_INT); - dsc.op2.u1.iconVal = 0; - dsc.op2.SetIconFlag(GTF_EMPTY); + AssertionDsc dsc = AssertionDsc::CreateConstantBoundUnsigned(this, relopVN); AssertionIndex index = optAddAssertion(&dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; @@ -2076,15 +2028,8 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) if ((objVN != ValueNumStore::NoVN) && vnStore->IsVNTypeHandle(typeHndVN)) { - AssertionDsc assertion; - assertion.assertionKind = OAK_EQUAL; - assertion.op1.kind = O1K_EXACT_TYPE; - assertion.op1.vn = objVN; - assertion.op2.kind = O2K_CONST_INT; - assertion.op2.u1.iconVal = vnStore->CoercedConstantValue(typeHndVN); - assertion.op2.vn = typeHndVN; - assertion.op2.SetIconFlag(GTF_ICON_CLASS_HDL); - AssertionIndex index = optAddAssertion(&assertion); + AssertionDsc dsc = AssertionDsc::CreateSubtype(this, objVN, typeHndVN, /*exact*/ true); + AssertionIndex index = optAddAssertion(&dsc); // We don't need to create a complementary assertion here. We're only interested // in the assertion that the object is of a certain type. The opposite assertion @@ -2195,15 +2140,8 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) if ((objVN != ValueNumStore::NoVN) && vnStore->IsVNTypeHandle(typeHndVN)) { - AssertionDsc assertion; - assertion.op1.kind = O1K_SUBTYPE; - assertion.op1.vn = objVN; - assertion.op2.kind = O2K_CONST_INT; - assertion.op2.u1.iconVal = vnStore->CoercedConstantValue(typeHndVN); - assertion.op2.vn = typeHndVN; - assertion.op2.SetIconFlag(GTF_ICON_CLASS_HDL); - assertion.assertionKind = OAK_EQUAL; - AssertionIndex index = optAddAssertion(&assertion); + AssertionDsc dsc = AssertionDsc::CreateSubtype(this, objVN, typeHndVN, /*exact*/ false); + AssertionIndex index = optAddAssertion(&dsc); // We don't need to create a complementary assertion here. We're only interested // in the assertion that the object is of a certain type. The opposite assertion @@ -5641,27 +5579,20 @@ bool Compiler::optCreateJumpTableImpliedAssertions(BasicBlock* switchBb) // NOTE: if offset != 0, we only know that "X + offset >= maxJumpIdx", which is not very useful. if ((offset == 0) && (value > 0) && !vnStore->IsVNConstant(opVN)) { - AssertionDsc dsc = {}; - dsc.assertionKind = OAK_NOT_EQUAL; - dsc.op2.kind = O2K_CONST_INT; - dsc.op2.vn = vnStore->VNZeroForType(TYP_INT); - dsc.op2.u1.iconVal = 0; - dsc.op2.SetIconFlag(GTF_EMPTY); if (vnStore->IsVNNeverNegative(opVN)) { // Create "X >= value" assertion (both operands are never negative) - dsc.op1.kind = O1K_CONSTANT_LOOP_BND; - dsc.op1.vn = vnStore->VNForFunc(TYP_INT, VNF_GE, opVN, vnStore->VNForIntCon(value)); - assert(vnStore->IsVNConstantBound(dsc.op1.vn)); + ValueNum relop = vnStore->VNForFunc(TYP_INT, VNF_GE, opVN, vnStore->VNForIntCon(value)); + AssertionDsc dsc = AssertionDsc::CreateConstantBound(this, relop); + newAssertIdx = optAddAssertion(&dsc); } else { // Create "X u>= value" assertion - dsc.op1.kind = O1K_CONSTANT_LOOP_BND_UN; - dsc.op1.vn = vnStore->VNForFunc(TYP_INT, VNF_GE_UN, opVN, vnStore->VNForIntCon(value)); - assert(vnStore->IsVNConstantBoundUnsigned(dsc.op1.vn)); + ValueNum relop = vnStore->VNForFunc(TYP_INT, VNF_GE_UN, opVN, vnStore->VNForIntCon(value)); + AssertionDsc dsc = AssertionDsc::CreateConstantBoundUnsigned(this, relop); + newAssertIdx = optAddAssertion(&dsc); } - newAssertIdx = optAddAssertion(&dsc); } else { @@ -5671,15 +5602,10 @@ bool Compiler::optCreateJumpTableImpliedAssertions(BasicBlock* switchBb) else { // Create "VN == value" assertion. - AssertionDsc dsc = {}; - dsc.assertionKind = OAK_EQUAL; - dsc.op1.lclNum = BAD_VAR_NUM; // O1K_LCLVAR relies only on op1.vn in Global Assertion Prop - dsc.op1.vn = opVN; - dsc.op1.kind = O1K_LCLVAR; - dsc.op2.vn = vnStore->VNForIntCon(value); - dsc.op2.u1.iconVal = value; - dsc.op2.kind = O2K_CONST_INT; - dsc.op2.SetIconFlag(GTF_EMPTY); + // TODO-Cleanup: Should use O1K_VN instead of O1K_LCLVAR + ValueNum valueVN = vnStore->VNForIntCon(value); + AssertionDsc dsc = + AssertionDsc::CreateConstantLclvarAssertion(this, BAD_VAR_NUM, opVN, value, valueVN, true); newAssertIdx = optAddAssertion(&dsc); } diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 72e049f1e5aa2d..9f65cb8ec0d0cc 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7995,6 +7995,175 @@ class Compiler return HasSameOp1(that, vnBased) && HasSameOp2(that, vnBased); } } + + static AssertionDsc CreateCompareCheckedBound(Compiler* comp, ValueNum relopVN) + { + assert(comp->vnStore->IsVNCompareCheckedBound(relopVN)); + + AssertionDsc dsc = {}; + dsc.assertionKind = OAK_NOT_EQUAL; + dsc.op1.kind = O1K_BOUND_LOOP_BND; + dsc.op1.vn = relopVN; + dsc.op2.kind = O2K_CONST_INT; + dsc.op2.vn = comp->vnStore->VNZeroForType(TYP_INT); + dsc.op2.u1.iconVal = 0; + dsc.op2.SetIconFlag(GTF_EMPTY); + return dsc; + } + + static AssertionDsc CreateCompareCheckedBoundArith(Compiler* comp, ValueNum relopVN) + { + assert(comp->vnStore->IsVNCompareCheckedBoundArith(relopVN)); + + AssertionDsc dsc = {}; + dsc.assertionKind = OAK_NOT_EQUAL; + dsc.op1.kind = O1K_BOUND_OPER_BND; + dsc.op1.vn = relopVN; + dsc.op2.kind = O2K_CONST_INT; + dsc.op2.vn = comp->vnStore->VNZeroForType(TYP_INT); + dsc.op2.u1.iconVal = 0; + dsc.op2.SetIconFlag(GTF_EMPTY); + return dsc; + } + + static AssertionDsc CreateUnsignedCompareCheckedBound( + Compiler* comp, ValueNum relopVN, const ValueNumStore::UnsignedCompareCheckedBoundInfo& unsignedCompareBnd) + { + ValueNumStore::UnsignedCompareCheckedBoundInfo tmp; + assert(comp->vnStore->IsVNUnsignedCompareCheckedBound(relopVN, &tmp)); + assert(unsignedCompareBnd.vnIdx != ValueNumStore::NoVN); + assert((unsignedCompareBnd.cmpOper == VNF_LT_UN) || (unsignedCompareBnd.cmpOper == VNF_GE_UN)); + assert(comp->vnStore->IsVNCheckedBound(unsignedCompareBnd.vnBound)); + + AssertionDsc dsc = {}; + dsc.assertionKind = OAK_NO_THROW; + dsc.op1.kind = O1K_ARR_BND; + dsc.op1.vn = relopVN; + dsc.op1.bnd.vnIdx = unsignedCompareBnd.vnIdx; + dsc.op1.bnd.vnLen = comp->vnStore->VNNormalValue(unsignedCompareBnd.vnBound); + dsc.op2.kind = O2K_INVALID; + dsc.op2.vn = ValueNumStore::NoVN; + + return dsc; + } + + static AssertionDsc CreateConstantBound(Compiler* comp, ValueNum relopVN) + { + assert(comp->vnStore->IsVNConstantBound(relopVN)); + + AssertionDsc dsc = {}; + dsc.assertionKind = OAK_NOT_EQUAL; + dsc.op1.kind = O1K_CONSTANT_LOOP_BND; + dsc.op1.vn = relopVN; + dsc.op2.kind = O2K_CONST_INT; + dsc.op2.vn = comp->vnStore->VNZeroForType(TYP_INT); + dsc.op2.u1.iconVal = 0; + dsc.op2.SetIconFlag(GTF_EMPTY); + return dsc; + } + + static AssertionDsc CreateConstantLclvarAssertion( + Compiler* comp, unsigned lclNum, ValueNum vn, int cns, ValueNum cnsVN, bool equals) + { + AssertionDsc dsc = {}; + dsc.assertionKind = equals ? OAK_EQUAL : OAK_NOT_EQUAL; + dsc.op1.kind = O1K_LCLVAR; + dsc.op2.kind = O2K_CONST_INT; + dsc.op2.u1.iconVal = cns; + dsc.op2.SetIconFlag(GTF_EMPTY); + + if (comp->optLocalAssertionProp) + { + assert(lclNum != BAD_VAR_NUM); + dsc.op1.vn = ValueNumStore::NoVN; + dsc.op2.vn = ValueNumStore::NoVN; + dsc.op1.lclNum = lclNum; + + // It seems like somewhere in local-AP we check op2.vn for being ValueNumStore::VNForNull + // while we shouldn't. TODO: remove this quirk. + if (cnsVN == ValueNumStore::VNForNull()) + { + dsc.op2.vn = ValueNumStore::VNForNull(); + } + } + else + { + assert(vn != ValueNumStore::NoVN); + assert(cnsVN != ValueNumStore::NoVN); + dsc.op1.lclNum = BAD_VAR_NUM; + dsc.op1.vn = vn; + dsc.op2.vn = cnsVN; + } + return dsc; + } + + static AssertionDsc CreateNonNullAssertion(Compiler* comp, unsigned lclNum, ValueNum vn) + { + return CreateConstantLclvarAssertion(comp, lclNum, vn, 0, ValueNumStore::VNForNull(), false); + } + + static AssertionDsc CreateInt32ConstantVNAssertion(Compiler* comp, ValueNum op1VN, ValueNum op2VN, bool equals) + { + assert(op1VN != ValueNumStore::NoVN); + assert(op1VN != ValueNumStore::NoVN); + assert(comp->vnStore->IsVNInt32Constant(op2VN)); + assert(!comp->vnStore->IsVNHandle(op2VN)); + assert(!comp->optLocalAssertionProp); + + AssertionDsc dsc = {}; + dsc.assertionKind = equals ? OAK_EQUAL : OAK_NOT_EQUAL; + dsc.op1.lclNum = BAD_VAR_NUM; + dsc.op1.vn = op1VN; + dsc.op2.vn = op2VN; + dsc.op1.kind = O1K_VN; + dsc.op2.kind = O2K_CONST_INT; + dsc.op2.u1.iconVal = comp->vnStore->ConstantValue(op2VN); + dsc.op2.SetIconFlag(GTF_EMPTY); + return dsc; + } + + static AssertionDsc CreateConstantBoundUnsigned(Compiler* comp, ValueNum relopVN) + { + assert(comp->vnStore->IsVNConstantBoundUnsigned(relopVN)); + + AssertionDsc dsc = {}; + dsc.assertionKind = OAK_NOT_EQUAL; + dsc.op1.kind = O1K_CONSTANT_LOOP_BND_UN; + dsc.op1.vn = relopVN; + dsc.op2.kind = O2K_CONST_INT; + dsc.op2.vn = comp->vnStore->VNZeroForType(TYP_INT); + dsc.op2.u1.iconVal = 0; + dsc.op2.SetIconFlag(GTF_EMPTY); + return dsc; + } + + static AssertionDsc CreateSubtype(Compiler* comp, ValueNum objVN, ValueNum typeHndVN, bool exact) + { + assert((objVN != ValueNumStore::NoVN) && comp->vnStore->IsVNTypeHandle(typeHndVN)); + + AssertionDsc dsc = {}; + dsc.op1.kind = exact ? O1K_EXACT_TYPE : O1K_SUBTYPE; + dsc.op1.vn = objVN; + dsc.op2.kind = O2K_CONST_INT; + dsc.op2.u1.iconVal = comp->vnStore->CoercedConstantValue(typeHndVN); + dsc.op2.vn = typeHndVN; + dsc.op2.SetIconFlag(GTF_ICON_CLASS_HDL); + dsc.assertionKind = OAK_EQUAL; + return dsc; + } + + static AssertionDsc CreateNoThrowArrBnd(Compiler* comp, ValueNum idxVN, ValueNum lenVN) + { + assert(idxVN != ValueNumStore::NoVN); + assert(lenVN != ValueNumStore::NoVN); + + AssertionDsc dsc = {}; + dsc.assertionKind = OAK_NO_THROW; + dsc.op1.kind = O1K_ARR_BND; + dsc.op1.bnd.vnIdx = idxVN; + dsc.op1.bnd.vnLen = lenVN; + return dsc; + } }; protected: From 14aa4e5798975e1690d8297e997e5b3dbd862f2d Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 31 Jan 2026 19:00:51 +0100 Subject: [PATCH 02/17] More --- src/coreclr/jit/assertionprop.cpp | 78 +++++++++++++------------------ src/coreclr/jit/compiler.h | 41 +++++++++++++++- 2 files changed, 73 insertions(+), 46 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 999ddc1efb6f58..32e706b1854761 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1142,9 +1142,6 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser { assert(op1 != nullptr); - AssertionDsc assertion = {OAK_INVALID}; - assert(assertion.assertionKind == OAK_INVALID); - if (op1->OperIs(GT_BOUNDS_CHECK) && (assertionKind == OAK_NO_THROW)) { ValueNum idxVN = optConservativeNormalVN(op1->AsBoundsChk()->GetIndex()); @@ -1153,7 +1150,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser { return NO_ASSERTION_INDEX; } - assertion = AssertionDsc::CreateNoThrowArrBnd(this, idxVN, lenVN); + AssertionDsc assertion = AssertionDsc::CreateNoThrowArrBnd(this, idxVN, lenVN); return optFinalizeCreatingAssertion(&assertion); } // @@ -1196,7 +1193,8 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser { return NO_ASSERTION_INDEX; } - assertion = AssertionDsc::CreateNonNullAssertion(this, lclNum, op1VN); + AssertionDsc assertion = AssertionDsc::CreateNonNullAssertion(this, lclNum, op1VN); + return optFinalizeCreatingAssertion(&assertion); } } // @@ -1211,7 +1209,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser // if (lclVar->IsAddressExposed()) { - goto DONE_ASSERTION; // Don't make an assertion + return NO_ASSERTION_INDEX; } { @@ -1221,10 +1219,6 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser op2 = op2->AsOp()->gtOp2; } - assertion.op1.kind = O1K_LCLVAR; - assertion.op1.lclNum = lclNum; - assertion.op1.vn = optConservativeNormalVN(op1); - switch (op2->gtOper) { optOp2Kind op2Kind; @@ -1255,11 +1249,16 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser // if ((assertionKind != OAK_EQUAL) && (assertionKind != OAK_NOT_EQUAL)) { - goto DONE_ASSERTION; // Don't make an assertion + return NO_ASSERTION_INDEX; } - assertion.op2.kind = op2Kind; - assertion.op2.vn = optConservativeNormalVN(op2); + AssertionDsc assertion; + assertion.assertionKind = assertionKind; + assertion.op1.kind = O1K_LCLVAR; + assertion.op1.lclNum = lclNum; + assertion.op1.vn = optConservativeNormalVN(op1); + assertion.op2.kind = op2Kind; + assertion.op2.vn = optConservativeNormalVN(op2); if (op2->OperIs(GT_CNS_INT)) { @@ -1272,7 +1271,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser // This assertion would be saying that a small local is equal to a value // outside its range. It means this block is unreachable. Avoid creating // such impossible assertions which can hit assertions in other places. - goto DONE_ASSERTION; + return NO_ASSERTION_INDEX; } iconVal = truncatedIconVal; @@ -1290,7 +1289,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser /* If we have an NaN value then don't record it */ if (FloatingPointUtils::isNaN(op2->AsDblCon()->DconValue())) { - goto DONE_ASSERTION; // Don't make an assertion + return NO_ASSERTION_INDEX; } assertion.op2.dconVal = op2->AsDblCon()->DconValue(); } @@ -1298,9 +1297,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser // // Ok everything has been set and the assertion looks good // - assertion.assertionKind = assertionKind; - - goto DONE_ASSERTION; + return optFinalizeCreatingAssertion(&assertion); } case GT_LCL_VAR: @@ -1308,13 +1305,13 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser if (!optLocalAssertionProp) { // O2K_LCLVAR_COPY is local assertion prop only - goto DONE_ASSERTION; + return NO_ASSERTION_INDEX; } // Must either be an OAK_EQUAL or an OAK_NOT_EQUAL assertion if ((assertionKind != OAK_EQUAL) && (assertionKind != OAK_NOT_EQUAL)) { - goto DONE_ASSERTION; // Don't make an assertion + return NO_ASSERTION_INDEX; } unsigned lclNum2 = op2->AsLclVarCommon()->GetLclNum(); @@ -1323,26 +1320,26 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser // If the two locals are the same then bail if (lclNum == lclNum2) { - goto DONE_ASSERTION; // Don't make an assertion + return NO_ASSERTION_INDEX; } // If the types are different then bail */ if (lclVar->lvType != lclVar2->lvType) { - goto DONE_ASSERTION; // Don't make an assertion + return NO_ASSERTION_INDEX; } // If we're making a copy of a "normalize on load" lclvar then the destination // has to be "normalize on load" as well, otherwise we risk skipping normalization. if (lclVar2->lvNormalizeOnLoad() && !lclVar->lvNormalizeOnLoad()) { - goto DONE_ASSERTION; // Don't make an assertion + return NO_ASSERTION_INDEX; } // If the local variable has its address exposed then bail if (lclVar2->IsAddressExposed()) { - goto DONE_ASSERTION; // Don't make an assertion + return NO_ASSERTION_INDEX; } // We process locals when we see the LCL_VAR node instead @@ -1357,17 +1354,13 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser // │ └──▌ LCL_FLD int V03 loc1 [+4] if (lclVar2->lvRedefinedInEmbeddedStatement) { - goto DONE_ASSERTION; // Don't make an assertion + return NO_ASSERTION_INDEX; } - assertion.op2.kind = O2K_LCLVAR_COPY; - assertion.op2.vn = optConservativeNormalVN(op2); - assertion.op2.lclNum = lclNum2; - // Ok everything has been set and the assertion looks good - assertion.assertionKind = assertionKind; - - goto DONE_ASSERTION; + AssertionDsc assertion = + AssertionDsc::CreateLclvarCopy(this, lclNum, lclNum2, assertionKind == OAK_EQUAL); + return optFinalizeCreatingAssertion(&assertion); } case GT_CALL: @@ -1377,10 +1370,8 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser GenTreeCall* const call = op2->AsCall(); if (call->IsHelperCall() && s_helperCallProperties.NonNullReturn(call->GetHelperNum())) { - assertion.assertionKind = OAK_NOT_EQUAL; - assertion.op2.kind = O2K_CONST_INT; - assertion.op2.u1.iconVal = 0; - goto DONE_ASSERTION; + AssertionDsc assertion = AssertionDsc::CreateNonNullAssertion(this, lclNum); + return optFinalizeCreatingAssertion(&assertion); } } break; @@ -1399,9 +1390,8 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser if (!typeRange.Equals(nodeRange)) { - assertion.op2.kind = O2K_SUBRANGE; - assertion.assertionKind = OAK_SUBRANGE; - assertion.op2.u2 = nodeRange; + AssertionDsc assertion = AssertionDsc::CreateSubrange(this, lclNum, nodeRange); + return optFinalizeCreatingAssertion(&assertion); } } } @@ -1420,15 +1410,13 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser !vnStore->IsVNHandle(op2VN)) { assert(assertionKind == OAK_EQUAL || assertionKind == OAK_NOT_EQUAL); - assertion = + AssertionDsc assertion = AssertionDsc::CreateInt32ConstantVNAssertion(this, op1VN, op2VN, assertionKind == OAK_EQUAL); return optAddAssertion(&assertion); } } } - -DONE_ASSERTION: - return optFinalizeCreatingAssertion(&assertion); + return NO_ASSERTION_INDEX; } //------------------------------------------------------------------------ @@ -1821,8 +1809,8 @@ void Compiler::optCreateComplementaryAssertion(AssertionIndex assertionIndex, Ge (candidateAssertion.op1.kind == O1K_CONSTANT_LOOP_BND) || (candidateAssertion.op1.kind == O1K_CONSTANT_LOOP_BND_UN)) { - AssertionDsc dsc = candidateAssertion; - dsc.assertionKind = dsc.assertionKind == OAK_EQUAL ? OAK_NOT_EQUAL : OAK_EQUAL; + AssertionDsc dsc = candidateAssertion; + dsc.ReverseEquality(); optAddAssertion(&dsc); return; } diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 9f65cb8ec0d0cc..0362eb5fa26df4 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7895,6 +7895,12 @@ class Compiler return assertionKind == OAK_SUBRANGE && op1.kind == O1K_LCLVAR; } + void ReverseEquality() + { + assert((assertionKind == OAK_EQUAL) || (assertionKind == OAK_NOT_EQUAL)); + assertionKind = assertionKind == OAK_EQUAL ? OAK_NOT_EQUAL : OAK_EQUAL; + } + static bool SameKind(AssertionDsc* a1, AssertionDsc* a2) { return a1->assertionKind == a2->assertionKind && a1->op1.kind == a2->op1.kind && @@ -8097,11 +8103,44 @@ class Compiler return dsc; } - static AssertionDsc CreateNonNullAssertion(Compiler* comp, unsigned lclNum, ValueNum vn) + static AssertionDsc CreateNonNullAssertion(Compiler* comp, unsigned lclNum, ValueNum vn = ValueNumStore::NoVN) { return CreateConstantLclvarAssertion(comp, lclNum, vn, 0, ValueNumStore::VNForNull(), false); } + static AssertionDsc CreateLclvarCopy(Compiler* comp, unsigned lclNum1, unsigned lclNum2, bool equals) + { + assert(comp->optLocalAssertionProp); + assert(lclNum1 != BAD_VAR_NUM); + assert(lclNum2 != BAD_VAR_NUM); + + AssertionDsc dsc = {}; + dsc.assertionKind = equals ? OAK_EQUAL : OAK_NOT_EQUAL; + dsc.op1.kind = O1K_LCLVAR; + dsc.op1.vn = ValueNumStore::NoVN; + dsc.op1.lclNum = lclNum1; + dsc.op2.vn = ValueNumStore::NoVN; + dsc.op2.lclNum = lclNum2; + dsc.op2.kind = O2K_LCLVAR_COPY; + return dsc; + } + + static AssertionDsc CreateSubrange(Compiler* comp, unsigned lclNum, const IntegralRange& range) + { + assert(comp->optLocalAssertionProp); + assert(lclNum != BAD_VAR_NUM); + + AssertionDsc dsc = {}; + dsc.assertionKind = OAK_SUBRANGE; + dsc.op1.kind = O1K_LCLVAR; + dsc.op1.vn = ValueNumStore::NoVN; + dsc.op1.lclNum = lclNum; + dsc.op2.vn = ValueNumStore::NoVN; + dsc.op2.kind = O2K_SUBRANGE; + dsc.op2.u2 = range; + return dsc; + } + static AssertionDsc CreateInt32ConstantVNAssertion(Compiler* comp, ValueNum op1VN, ValueNum op2VN, bool equals) { assert(op1VN != ValueNumStore::NoVN); From e74dcbfd88cb9152b583f5a1225298895cf5adaf Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 31 Jan 2026 20:04:41 +0100 Subject: [PATCH 03/17] More --- src/coreclr/jit/assertionprop.cpp | 175 +++++++++++++----------------- src/coreclr/jit/compiler.h | 43 ++++++-- 2 files changed, 107 insertions(+), 111 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 32e706b1854761..affb3b8807fee4 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1138,29 +1138,14 @@ ssize_t Compiler::optCastConstantSmall(ssize_t iconVal, var_types smallType) // Assertion creation may fail either because the provided assertion // operands aren't supported or because the assertion table is full. // -AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAssertionKind assertionKind) +AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equals) { assert(op1 != nullptr); - if (op1->OperIs(GT_BOUNDS_CHECK) && (assertionKind == OAK_NO_THROW)) - { - ValueNum idxVN = optConservativeNormalVN(op1->AsBoundsChk()->GetIndex()); - ValueNum lenVN = optConservativeNormalVN(op1->AsBoundsChk()->GetArrayLength()); - if ((idxVN == ValueNumStore::NoVN) || (lenVN == ValueNumStore::NoVN)) - { - return NO_ASSERTION_INDEX; - } - AssertionDsc assertion = AssertionDsc::CreateNoThrowArrBnd(this, idxVN, lenVN); - return optFinalizeCreatingAssertion(&assertion); - } - // - // Are we trying to make a non-null assertion? - // (note we now do this for all indirs, regardless of address type) - // - else if (op2 == nullptr) + if (op2 == nullptr) { // Must be an OAK_NOT_EQUAL assertion - assert(assertionKind == OAK_NOT_EQUAL); + assert(!equals); // Set op1 to the instance pointer of the indirection op1 = op1->gtEffectiveVal(); @@ -1221,83 +1206,68 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser switch (op2->gtOper) { - optOp2Kind op2Kind; - // // Constant Assertions // - case GT_CNS_INT: - if (op1->TypeIs(TYP_STRUCT)) + case GT_CNS_DBL: + { + if (FloatingPointUtils::isNaN(op2->AsDblCon()->DconValue())) { - assert(op2->IsIntegralConst(0)); - op2Kind = O2K_ZEROOBJ; + return NO_ASSERTION_INDEX; } - else + + ValueNum op1VN = optConservativeNormalVN(op1); + ValueNum op2VN = optConservativeNormalVN(op2); + if (!optLocalAssertionProp && (op1VN == ValueNumStore::NoVN || op2VN == ValueNumStore::NoVN)) { - op2Kind = O2K_CONST_INT; + return NO_ASSERTION_INDEX; } - goto CNS_COMMON; - - case GT_CNS_DBL: - op2Kind = O2K_CONST_DOUBLE; - goto CNS_COMMON; + AssertionDsc dsc = + AssertionDsc::CreateConstantLclvarAssertion(this, lclNum, op1VN, op2->AsDblCon()->DconValue(), + op2VN, equals); + return optFinalizeCreatingAssertion(&dsc); + } - CNS_COMMON: + case GT_CNS_INT: { - // - // Must either be an OAK_EQUAL or an OAK_NOT_EQUAL assertion - // - if ((assertionKind != OAK_EQUAL) && (assertionKind != OAK_NOT_EQUAL)) + ValueNum op1VN = optConservativeNormalVN(op1); + ValueNum op2VN = optConservativeNormalVN(op2); + if (!optLocalAssertionProp && (op1VN == ValueNumStore::NoVN || op2VN == ValueNumStore::NoVN)) { return NO_ASSERTION_INDEX; } - AssertionDsc assertion; - assertion.assertionKind = assertionKind; - assertion.op1.kind = O1K_LCLVAR; - assertion.op1.lclNum = lclNum; - assertion.op1.vn = optConservativeNormalVN(op1); - assertion.op2.kind = op2Kind; - assertion.op2.vn = optConservativeNormalVN(op2); - - if (op2->OperIs(GT_CNS_INT)) + if (op1->TypeIs(TYP_STRUCT)) { - ssize_t iconVal = op2->AsIntCon()->IconValue(); - if (varTypeIsSmall(lclVar)) - { - ssize_t truncatedIconVal = optCastConstantSmall(iconVal, lclVar->TypeGet()); - if (!op1->OperIs(GT_STORE_LCL_VAR) && (truncatedIconVal != iconVal)) - { - // This assertion would be saying that a small local is equal to a value - // outside its range. It means this block is unreachable. Avoid creating - // such impossible assertions which can hit assertions in other places. - return NO_ASSERTION_INDEX; - } - - iconVal = truncatedIconVal; - if (!optLocalAssertionProp) - { - assertion.op2.vn = vnStore->VNForIntCon(static_cast(iconVal)); - } - } - assertion.op2.u1.iconVal = iconVal; - assertion.op2.SetIconFlag(op2->GetIconHandleFlag(), op2->AsIntCon()->gtFieldSeq); + AssertionDsc dsc = + AssertionDsc::CreateConstantLclvarAssertion(this, lclNum, op1VN, + AssertionDsc::ZeroObj::instance, op2VN, equals); + return optFinalizeCreatingAssertion(&dsc); } - else + + ssize_t iconVal = op2->AsIntCon()->IconValue(); + if (varTypeIsSmall(lclVar)) { - noway_assert(op2->OperIs(GT_CNS_DBL)); - /* If we have an NaN value then don't record it */ - if (FloatingPointUtils::isNaN(op2->AsDblCon()->DconValue())) + ssize_t truncatedIconVal = optCastConstantSmall(iconVal, lclVar->TypeGet()); + if (!op1->OperIs(GT_STORE_LCL_VAR) && (truncatedIconVal != iconVal)) { + // This assertion would be saying that a small local is equal to a value + // outside its range. It means this block is unreachable. Avoid creating + // such impossible assertions which can hit assertions in other places. return NO_ASSERTION_INDEX; } - assertion.op2.dconVal = op2->AsDblCon()->DconValue(); + + iconVal = truncatedIconVal; + if (!optLocalAssertionProp) + { + op2VN = vnStore->VNForIntCon(static_cast(iconVal)); + } } - // - // Ok everything has been set and the assertion looks good - // - return optFinalizeCreatingAssertion(&assertion); + AssertionDsc dsc = + AssertionDsc::CreateConstantLclvarAssertion(this, lclNum, op1VN, iconVal, op2VN, equals); + dsc.op2.SetIconFlag(op2->GetIconHandleFlag(), op2->AsIntCon()->gtFieldSeq); + return optFinalizeCreatingAssertion(&dsc); } case GT_LCL_VAR: @@ -1308,12 +1278,6 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser return NO_ASSERTION_INDEX; } - // Must either be an OAK_EQUAL or an OAK_NOT_EQUAL assertion - if ((assertionKind != OAK_EQUAL) && (assertionKind != OAK_NOT_EQUAL)) - { - return NO_ASSERTION_INDEX; - } - unsigned lclNum2 = op2->AsLclVarCommon()->GetLclNum(); LclVarDsc* lclVar2 = lvaGetDesc(lclNum2); @@ -1358,8 +1322,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser } // Ok everything has been set and the assertion looks good - AssertionDsc assertion = - AssertionDsc::CreateLclvarCopy(this, lclNum, lclNum2, assertionKind == OAK_EQUAL); + AssertionDsc assertion = AssertionDsc::CreateLclvarCopy(this, lclNum, lclNum2, equals); return optFinalizeCreatingAssertion(&assertion); } @@ -1382,7 +1345,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser } // Try and see if we can make a subrange assertion. - if (((assertionKind == OAK_SUBRANGE) || (assertionKind == OAK_EQUAL)) && varTypeIsIntegral(op2)) + if (equals && varTypeIsIntegral(op2)) { IntegralRange nodeRange = IntegralRange::ForNode(op2, this); IntegralRange typeRange = IntegralRange::ForType(genActualType(op2)); @@ -1409,9 +1372,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, optAsser if (op1VN != ValueNumStore::NoVN && op2VN != ValueNumStore::NoVN && vnStore->IsVNInt32Constant(op2VN) && !vnStore->IsVNHandle(op2VN)) { - assert(assertionKind == OAK_EQUAL || assertionKind == OAK_NOT_EQUAL); - AssertionDsc assertion = - AssertionDsc::CreateInt32ConstantVNAssertion(this, op1VN, op2VN, assertionKind == OAK_EQUAL); + AssertionDsc assertion = AssertionDsc::CreateInt32ConstantVNAssertion(this, op1VN, op2VN, equals); return optAddAssertion(&assertion); } } @@ -1841,12 +1802,12 @@ void Compiler::optCreateComplementaryAssertion(AssertionIndex assertionIndex, Ge return; } - AssertionIndex index = optCreateAssertion(op1, op2, OAK_NOT_EQUAL); + AssertionIndex index = optCreateAssertion(op1, op2, /*equals*/ false); optMapComplementary(index, assertionIndex); } else if (candidateAssertion.assertionKind == OAK_NOT_EQUAL) { - AssertionIndex index = optCreateAssertion(op1, op2, OAK_EQUAL); + AssertionIndex index = optCreateAssertion(op1, op2, /*equals*/ true); optMapComplementary(index, assertionIndex); } } @@ -1870,9 +1831,9 @@ void Compiler::optCreateComplementaryAssertion(AssertionIndex assertionIndex, Ge // create a second, complementary assertion. This may too fail, for the // same reasons as the first one. // -AssertionIndex Compiler::optCreateJtrueAssertions(GenTree* op1, GenTree* op2, optAssertionKind assertionKind) +AssertionIndex Compiler::optCreateJtrueAssertions(GenTree* op1, GenTree* op2, bool equals) { - AssertionIndex assertionIndex = optCreateAssertion(op1, op2, assertionKind); + AssertionIndex assertionIndex = optCreateAssertion(op1, op2, equals); // Don't bother if we don't have an assertion on the JTrue False path. Current implementation // allows for a complementary only if there is an assertion on the False path (tree->HasAssertion()). if (assertionIndex != NO_ASSERTION_INDEX) @@ -1966,7 +1927,7 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) return NO_ASSERTION_INDEX; } - Compiler::optAssertionKind assertionKind = OAK_INVALID; + bool equals = true; AssertionInfo info = optCreateJTrueBoundsAssertion(tree); if (info.HasAssertion()) @@ -1983,10 +1944,10 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) switch (relop->gtOper) { case GT_EQ: - assertionKind = OAK_EQUAL; + equals = true; break; case GT_NE: - assertionKind = OAK_NOT_EQUAL; + equals = false; break; default: // TODO-CQ: add other relop operands. Disabled for now to measure perf @@ -2057,7 +2018,7 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) } } - return optCreateJtrueAssertions(op1, op2, assertionKind); + return optCreateJtrueAssertions(op1, op2, equals); } else if (!optLocalAssertionProp) { @@ -2067,7 +2028,7 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) if (vnStore->IsVNCheckedBound(op1VN) && vnStore->IsVNInt32Constant(op2VN)) { assert(relop->OperIs(GT_EQ, GT_NE)); - return optCreateJtrueAssertions(op1, op2, assertionKind); + return optCreateJtrueAssertions(op1, op2, equals); } } @@ -2080,7 +2041,7 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) // If op1 is ind, then extract op1's oper. if (op1->OperIs(GT_IND) && op1->AsOp()->gtOp1->OperIs(GT_LCL_VAR)) { - return optCreateJtrueAssertions(op1, op2, assertionKind); + return optCreateJtrueAssertions(op1, op2, equals); } // Look for a call to an IsInstanceOf helper compared to a nullptr @@ -2176,7 +2137,7 @@ void Compiler::optAssertionGen(GenTree* tree) // VN takes care of non local assertions for data flow. if (optLocalAssertionProp) { - assertionInfo = optCreateAssertion(tree, tree->AsLclVar()->Data(), OAK_EQUAL); + assertionInfo = optCreateAssertion(tree, tree->AsLclVar()->Data(), /*equals*/ true); } break; @@ -2196,27 +2157,37 @@ void Compiler::optAssertionGen(GenTree* tree) // These indirs (esp. GT_IND and GT_STOREIND) are the most popular sources of assertions. if (tree->IndirMayFault(this)) { - assertionInfo = optCreateAssertion(tree->GetIndirOrArrMetaDataAddr(), nullptr, OAK_NOT_EQUAL); + assertionInfo = optCreateAssertion(tree->GetIndirOrArrMetaDataAddr(), nullptr, /*equals*/ false); } break; case GT_INTRINSIC: if (tree->AsIntrinsic()->gtIntrinsicName == NI_System_Object_GetType) { - assertionInfo = optCreateAssertion(tree->AsIntrinsic()->gtGetOp1(), nullptr, OAK_NOT_EQUAL); + assertionInfo = optCreateAssertion(tree->AsIntrinsic()->gtGetOp1(), nullptr, /*equals*/ false); } break; case GT_BOUNDS_CHECK: if (!optLocalAssertionProp) { - assertionInfo = optCreateAssertion(tree, nullptr, OAK_NO_THROW); + ValueNum idxVN = optConservativeNormalVN(tree->AsBoundsChk()->GetIndex()); + ValueNum lenVN = optConservativeNormalVN(tree->AsBoundsChk()->GetArrayLength()); + if ((idxVN == ValueNumStore::NoVN) || (lenVN == ValueNumStore::NoVN)) + { + assertionInfo = NO_ASSERTION_INDEX; + } + else + { + AssertionDsc assertion = AssertionDsc::CreateNoThrowArrBnd(this, idxVN, lenVN); + assertionInfo = optFinalizeCreatingAssertion(&assertion); + } } break; case GT_ARR_ELEM: // An array element reference can create a non-null assertion - assertionInfo = optCreateAssertion(tree->AsArrElem()->gtArrObj, nullptr, OAK_NOT_EQUAL); + assertionInfo = optCreateAssertion(tree->AsArrElem()->gtArrObj, nullptr, /*equals*/ false); break; case GT_CALL: @@ -2230,7 +2201,7 @@ void Compiler::optAssertionGen(GenTree* tree) // Retrieve the 'this' arg. GenTree* thisArg = call->gtArgs.GetThisArg()->GetNode(); assert(thisArg != nullptr); - assertionInfo = optCreateAssertion(thisArg, nullptr, OAK_NOT_EQUAL); + assertionInfo = optCreateAssertion(thisArg, nullptr, /*equals*/ false); } } break; diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 0362eb5fa26df4..a365d1531c9e34 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8068,15 +8068,18 @@ class Compiler return dsc; } + struct ZeroObj + { + static ZeroObj instance; + }; + + template static AssertionDsc CreateConstantLclvarAssertion( - Compiler* comp, unsigned lclNum, ValueNum vn, int cns, ValueNum cnsVN, bool equals) + Compiler* comp, unsigned lclNum, ValueNum vn, T cns, ValueNum cnsVN, bool equals) { - AssertionDsc dsc = {}; - dsc.assertionKind = equals ? OAK_EQUAL : OAK_NOT_EQUAL; - dsc.op1.kind = O1K_LCLVAR; - dsc.op2.kind = O2K_CONST_INT; - dsc.op2.u1.iconVal = cns; - dsc.op2.SetIconFlag(GTF_EMPTY); + AssertionDsc dsc = {}; + dsc.assertionKind = equals ? OAK_EQUAL : OAK_NOT_EQUAL; + dsc.op1.kind = O1K_LCLVAR; if (comp->optLocalAssertionProp) { @@ -8100,6 +8103,28 @@ class Compiler dsc.op1.vn = vn; dsc.op2.vn = cnsVN; } + + if constexpr (std::is_same::value || std::is_same::value) + { + dsc.op2.kind = O2K_CONST_INT; + dsc.op2.u1.iconVal = static_cast(cns); + dsc.op2.SetIconFlag(GTF_EMPTY); + } + else if constexpr (std::is_same::value) + { + dsc.op2.kind = O2K_CONST_DOUBLE; + dsc.op2.dconVal = static_cast(cns); + dsc.op2.SetIconFlag(GTF_EMPTY); + } + else if constexpr (std::is_same::value) + { + dsc.op2.kind = O2K_ZEROOBJ; + dsc.op2.SetIconFlag(GTF_EMPTY); + } + else + { + static_assert(std::is_same::value, "Unexpected type for cns"); + } return dsc; } @@ -8269,7 +8294,7 @@ class Compiler AssertionIndex optAssertionGenCast(GenTreeCast* cast); AssertionInfo optCreateJTrueBoundsAssertion(GenTree* tree); AssertionInfo optAssertionGenJtrue(GenTree* tree); - AssertionIndex optCreateJtrueAssertions(GenTree* op1, GenTree* op2, optAssertionKind assertionKind); + AssertionIndex optCreateJtrueAssertions(GenTree* op1, GenTree* op2, bool equals); AssertionIndex optFindComplementary(AssertionIndex assertionIndex); void optMapComplementary(AssertionIndex assertionIndex, AssertionIndex index); @@ -8278,7 +8303,7 @@ class Compiler ssize_t optCastConstantSmall(ssize_t iconVal, var_types smallType); // Assertion creation functions. - AssertionIndex optCreateAssertion(GenTree* op1, GenTree* op2, optAssertionKind assertionKind); + AssertionIndex optCreateAssertion(GenTree* op1, GenTree* op2, bool equals); AssertionIndex optFinalizeCreatingAssertion(AssertionDsc* assertion); From 64aa20b3e46246cb20a65db8c6fec1142a595ec6 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 31 Jan 2026 21:01:32 +0100 Subject: [PATCH 04/17] create factory methods for assertions --- src/coreclr/jit/assertionprop.cpp | 88 +++++--------- src/coreclr/jit/compiler.h | 184 ++++++++++++++---------------- src/coreclr/jit/morph.cpp | 12 +- 3 files changed, 118 insertions(+), 166 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index affb3b8807fee4..d752fa0902e362 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1171,15 +1171,19 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ if (!fgIsBigOffset(offset) && op1->OperIs(GT_LCL_VAR) && !lvaVarAddrExposed(op1->AsLclVar()->GetLclNum())) { - ValueNum op1VN = optConservativeNormalVN(op1); - unsigned lclNum = op1->AsLclVar()->GetLclNum(); - if ((!optLocalAssertionProp && op1VN == ValueNumStore::NoVN) || - (optLocalAssertionProp && lclNum == BAD_VAR_NUM)) + if (optLocalAssertionProp) + { + AssertionDsc assertion = AssertionDsc::CreateLclNonNullAssertion(this, op1->AsLclVar()->GetLclNum()); + return optAddAssertion(&assertion); + } + + ValueNum op1VN = optConservativeNormalVN(op1); + if (op1VN == ValueNumStore::NoVN) { return NO_ASSERTION_INDEX; } - AssertionDsc assertion = AssertionDsc::CreateNonNullAssertion(this, lclNum, op1VN); - return optFinalizeCreatingAssertion(&assertion); + AssertionDsc assertion = AssertionDsc::CreateVNNonNullAssertion(this, op1VN); + return optAddAssertion(&assertion); } } // @@ -1225,7 +1229,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ AssertionDsc dsc = AssertionDsc::CreateConstantLclvarAssertion(this, lclNum, op1VN, op2->AsDblCon()->DconValue(), op2VN, equals); - return optFinalizeCreatingAssertion(&dsc); + return optAddAssertion(&dsc); } case GT_CNS_INT: @@ -1240,9 +1244,9 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ if (op1->TypeIs(TYP_STRUCT)) { AssertionDsc dsc = - AssertionDsc::CreateConstantLclvarAssertion(this, lclNum, op1VN, - AssertionDsc::ZeroObj::instance, op2VN, equals); - return optFinalizeCreatingAssertion(&dsc); + AssertionDsc::CreateConstantLclvarAssertion(this, lclNum, op1VN, 0, op2VN, equals); + dsc.op2.kind = O2K_ZEROOBJ; + return optAddAssertion(&dsc); } ssize_t iconVal = op2->AsIntCon()->IconValue(); @@ -1267,7 +1271,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ AssertionDsc dsc = AssertionDsc::CreateConstantLclvarAssertion(this, lclNum, op1VN, iconVal, op2VN, equals); dsc.op2.SetIconFlag(op2->GetIconHandleFlag(), op2->AsIntCon()->gtFieldSeq); - return optFinalizeCreatingAssertion(&dsc); + return optAddAssertion(&dsc); } case GT_LCL_VAR: @@ -1323,7 +1327,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ // Ok everything has been set and the assertion looks good AssertionDsc assertion = AssertionDsc::CreateLclvarCopy(this, lclNum, lclNum2, equals); - return optFinalizeCreatingAssertion(&assertion); + return optAddAssertion(&assertion); } case GT_CALL: @@ -1333,8 +1337,8 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ GenTreeCall* const call = op2->AsCall(); if (call->IsHelperCall() && s_helperCallProperties.NonNullReturn(call->GetHelperNum())) { - AssertionDsc assertion = AssertionDsc::CreateNonNullAssertion(this, lclNum); - return optFinalizeCreatingAssertion(&assertion); + AssertionDsc assertion = AssertionDsc::CreateLclNonNullAssertion(this, lclNum); + return optAddAssertion(&assertion); } } break; @@ -1354,7 +1358,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ if (!typeRange.Equals(nodeRange)) { AssertionDsc assertion = AssertionDsc::CreateSubrange(this, lclNum, nodeRange); - return optFinalizeCreatingAssertion(&assertion); + return optAddAssertion(&assertion); } } } @@ -1380,41 +1384,6 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ return NO_ASSERTION_INDEX; } -//------------------------------------------------------------------------ -// optFinalizeCreatingAssertion: Add the assertion, if well-formed, to the table. -// -// Checks that in global assertion propagation assertions do not have missing -// value and SSA numbers. -// -// Arguments: -// assertion - assertion to check and add to the table -// -// Return Value: -// Index of the assertion if it was successfully created, NO_ASSERTION_INDEX otherwise. -// -AssertionIndex Compiler::optFinalizeCreatingAssertion(AssertionDsc* assertion) -{ - if (assertion->assertionKind == OAK_INVALID) - { - return NO_ASSERTION_INDEX; - } - - if (!optLocalAssertionProp) - { - if ((assertion->op1.vn == ValueNumStore::NoVN) || (assertion->op2.vn == ValueNumStore::NoVN) || - (assertion->op1.vn == ValueNumStore::VNForVoid()) || (assertion->op2.vn == ValueNumStore::VNForVoid())) - { - return NO_ASSERTION_INDEX; - } - } - - // Now add the assertion to our assertion table - noway_assert(assertion->op1.kind != O1K_INVALID); - noway_assert((assertion->op1.kind == O1K_ARR_BND) || (assertion->op2.kind != O2K_INVALID)); - - return optAddAssertion(assertion); -} - /***************************************************************************** * * If tree is a constant node holding an integral value, retrieve the value in @@ -1866,7 +1835,7 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "i < bnd +/- k != 0" if (vnStore->IsVNCompareCheckedBoundArith(relopVN)) { - AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBoundArith(this, relopVN); + AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBoundArith(this, relopVN, /*withArith*/ true); AssertionIndex index = optAddAssertion(&dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; @@ -1876,7 +1845,7 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "i < bnd != 0" else if (vnStore->IsVNCompareCheckedBound(relopVN)) { - AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBound(this, relopVN); + AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBoundArith(this, relopVN, /*withArith*/ false); AssertionIndex index = optAddAssertion(&dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; @@ -1885,7 +1854,10 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "no throw" since this condition guarantees that i is both >= 0 and < bnd (on the appropriate edge) else if (vnStore->IsVNUnsignedCompareCheckedBound(relopVN, &unsignedCompareBnd)) { - AssertionDsc dsc = AssertionDsc::CreateUnsignedCompareCheckedBound(this, relopVN, unsignedCompareBnd); + ValueNum idxVN = vnStore->VNNormalValue(unsignedCompareBnd.vnIdx); + ValueNum lenVN = vnStore->VNNormalValue(unsignedCompareBnd.vnBound); + + AssertionDsc dsc = AssertionDsc::CreateNoThrowArrBnd(this, idxVN, lenVN); AssertionIndex index = optAddAssertion(&dsc); if (unsignedCompareBnd.cmpOper == VNF_GE_UN) { @@ -1900,14 +1872,14 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) // Assertion: "i < 100 != 0" else if (vnStore->IsVNConstantBound(relopVN)) { - AssertionDsc dsc = AssertionDsc::CreateConstantBound(this, relopVN); + AssertionDsc dsc = AssertionDsc::CreateConstantLoopBound(this, relopVN, /*isUnsigned*/ false); AssertionIndex index = optAddAssertion(&dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; } else if (vnStore->IsVNConstantBoundUnsigned(relopVN)) { - AssertionDsc dsc = AssertionDsc::CreateConstantBoundUnsigned(this, relopVN); + AssertionDsc dsc = AssertionDsc::CreateConstantLoopBound(this, relopVN, /*isUnsigned*/ true); AssertionIndex index = optAddAssertion(&dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; @@ -2180,7 +2152,7 @@ void Compiler::optAssertionGen(GenTree* tree) else { AssertionDsc assertion = AssertionDsc::CreateNoThrowArrBnd(this, idxVN, lenVN); - assertionInfo = optFinalizeCreatingAssertion(&assertion); + assertionInfo = optAddAssertion(&assertion); } } break; @@ -5542,14 +5514,14 @@ bool Compiler::optCreateJumpTableImpliedAssertions(BasicBlock* switchBb) { // Create "X >= value" assertion (both operands are never negative) ValueNum relop = vnStore->VNForFunc(TYP_INT, VNF_GE, opVN, vnStore->VNForIntCon(value)); - AssertionDsc dsc = AssertionDsc::CreateConstantBound(this, relop); + AssertionDsc dsc = AssertionDsc::CreateConstantLoopBound(this, relop, /*isUnsigned*/ false); newAssertIdx = optAddAssertion(&dsc); } else { // Create "X u>= value" assertion ValueNum relop = vnStore->VNForFunc(TYP_INT, VNF_GE_UN, opVN, vnStore->VNForIntCon(value)); - AssertionDsc dsc = AssertionDsc::CreateConstantBoundUnsigned(this, relop); + AssertionDsc dsc = AssertionDsc::CreateConstantLoopBound(this, relop, /*isUnsigned*/ true); newAssertIdx = optAddAssertion(&dsc); } } diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index a365d1531c9e34..eb134034586323 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8002,77 +8002,11 @@ class Compiler } } - static AssertionDsc CreateCompareCheckedBound(Compiler* comp, ValueNum relopVN) - { - assert(comp->vnStore->IsVNCompareCheckedBound(relopVN)); - - AssertionDsc dsc = {}; - dsc.assertionKind = OAK_NOT_EQUAL; - dsc.op1.kind = O1K_BOUND_LOOP_BND; - dsc.op1.vn = relopVN; - dsc.op2.kind = O2K_CONST_INT; - dsc.op2.vn = comp->vnStore->VNZeroForType(TYP_INT); - dsc.op2.u1.iconVal = 0; - dsc.op2.SetIconFlag(GTF_EMPTY); - return dsc; - } - - static AssertionDsc CreateCompareCheckedBoundArith(Compiler* comp, ValueNum relopVN) - { - assert(comp->vnStore->IsVNCompareCheckedBoundArith(relopVN)); - - AssertionDsc dsc = {}; - dsc.assertionKind = OAK_NOT_EQUAL; - dsc.op1.kind = O1K_BOUND_OPER_BND; - dsc.op1.vn = relopVN; - dsc.op2.kind = O2K_CONST_INT; - dsc.op2.vn = comp->vnStore->VNZeroForType(TYP_INT); - dsc.op2.u1.iconVal = 0; - dsc.op2.SetIconFlag(GTF_EMPTY); - return dsc; - } - - static AssertionDsc CreateUnsignedCompareCheckedBound( - Compiler* comp, ValueNum relopVN, const ValueNumStore::UnsignedCompareCheckedBoundInfo& unsignedCompareBnd) - { - ValueNumStore::UnsignedCompareCheckedBoundInfo tmp; - assert(comp->vnStore->IsVNUnsignedCompareCheckedBound(relopVN, &tmp)); - assert(unsignedCompareBnd.vnIdx != ValueNumStore::NoVN); - assert((unsignedCompareBnd.cmpOper == VNF_LT_UN) || (unsignedCompareBnd.cmpOper == VNF_GE_UN)); - assert(comp->vnStore->IsVNCheckedBound(unsignedCompareBnd.vnBound)); - - AssertionDsc dsc = {}; - dsc.assertionKind = OAK_NO_THROW; - dsc.op1.kind = O1K_ARR_BND; - dsc.op1.vn = relopVN; - dsc.op1.bnd.vnIdx = unsignedCompareBnd.vnIdx; - dsc.op1.bnd.vnLen = comp->vnStore->VNNormalValue(unsignedCompareBnd.vnBound); - dsc.op2.kind = O2K_INVALID; - dsc.op2.vn = ValueNumStore::NoVN; - - return dsc; - } - - static AssertionDsc CreateConstantBound(Compiler* comp, ValueNum relopVN) - { - assert(comp->vnStore->IsVNConstantBound(relopVN)); - - AssertionDsc dsc = {}; - dsc.assertionKind = OAK_NOT_EQUAL; - dsc.op1.kind = O1K_CONSTANT_LOOP_BND; - dsc.op1.vn = relopVN; - dsc.op2.kind = O2K_CONST_INT; - dsc.op2.vn = comp->vnStore->VNZeroForType(TYP_INT); - dsc.op2.u1.iconVal = 0; - dsc.op2.SetIconFlag(GTF_EMPTY); - return dsc; - } - - struct ZeroObj - { - static ZeroObj instance; - }; + // + // Factory methods for common assertions + // + // Create a generic "lclNum ==/!= constant" or "vn ==/!= constant" assertion template static AssertionDsc CreateConstantLclvarAssertion( Compiler* comp, unsigned lclNum, ValueNum vn, T cns, ValueNum cnsVN, bool equals) @@ -8088,8 +8022,8 @@ class Compiler dsc.op2.vn = ValueNumStore::NoVN; dsc.op1.lclNum = lclNum; - // It seems like somewhere in local-AP we check op2.vn for being ValueNumStore::VNForNull - // while we shouldn't. TODO: remove this quirk. + // TODO-Quirk: Somewhere in local-AP we check op2.vn for being ValueNumStore::VNForNull + // while we shouldn't. Remove it. if (cnsVN == ValueNumStore::VNForNull()) { dsc.op2.vn = ValueNumStore::VNForNull(); @@ -8104,35 +8038,39 @@ class Compiler dsc.op2.vn = cnsVN; } - if constexpr (std::is_same::value || std::is_same::value) + dsc.op2.SetIconFlag(GTF_EMPTY); + if constexpr (std::is_same_v || std::is_same_v) { dsc.op2.kind = O2K_CONST_INT; dsc.op2.u1.iconVal = static_cast(cns); - dsc.op2.SetIconFlag(GTF_EMPTY); } - else if constexpr (std::is_same::value) + else if constexpr (std::is_same_v) { dsc.op2.kind = O2K_CONST_DOUBLE; dsc.op2.dconVal = static_cast(cns); - dsc.op2.SetIconFlag(GTF_EMPTY); - } - else if constexpr (std::is_same::value) - { - dsc.op2.kind = O2K_ZEROOBJ; - dsc.op2.SetIconFlag(GTF_EMPTY); } else { - static_assert(std::is_same::value, "Unexpected type for cns"); + static_assert("Unexpected type for cns"); } return dsc; } - static AssertionDsc CreateNonNullAssertion(Compiler* comp, unsigned lclNum, ValueNum vn = ValueNumStore::NoVN) + // Create "lclNum != null" assertion + static AssertionDsc CreateLclNonNullAssertion(Compiler* comp, unsigned lclNum) + { + return CreateConstantLclvarAssertion(comp, lclNum, ValueNumStore::NoVN, 0, ValueNumStore::VNForNull(), + /*equals*/ false); + } + + // Create "vn != null" assertion + static AssertionDsc CreateVNNonNullAssertion(Compiler* comp, ValueNum vn) { - return CreateConstantLclvarAssertion(comp, lclNum, vn, 0, ValueNumStore::VNForNull(), false); + return CreateConstantLclvarAssertion(comp, BAD_VAR_NUM, vn, 0, ValueNumStore::VNForNull(), + /*equals*/ false); } + // Create "lclNum1 ==/!= lclNum2" copy assertion static AssertionDsc CreateLclvarCopy(Compiler* comp, unsigned lclNum1, unsigned lclNum2, bool equals) { assert(comp->optLocalAssertionProp); @@ -8150,6 +8088,7 @@ class Compiler return dsc; } + // Create "lclNum in range [lowerBound, upperBound]" assertion static AssertionDsc CreateSubrange(Compiler* comp, unsigned lclNum, const IntegralRange& range) { assert(comp->optLocalAssertionProp); @@ -8166,6 +8105,7 @@ class Compiler return dsc; } + // Create "VN ==/!= int32_constant" assertion static AssertionDsc CreateInt32ConstantVNAssertion(Compiler* comp, ValueNum op1VN, ValueNum op2VN, bool equals) { assert(op1VN != ValueNumStore::NoVN); @@ -8186,21 +8126,7 @@ class Compiler return dsc; } - static AssertionDsc CreateConstantBoundUnsigned(Compiler* comp, ValueNum relopVN) - { - assert(comp->vnStore->IsVNConstantBoundUnsigned(relopVN)); - - AssertionDsc dsc = {}; - dsc.assertionKind = OAK_NOT_EQUAL; - dsc.op1.kind = O1K_CONSTANT_LOOP_BND_UN; - dsc.op1.vn = relopVN; - dsc.op2.kind = O2K_CONST_INT; - dsc.op2.vn = comp->vnStore->VNZeroForType(TYP_INT); - dsc.op2.u1.iconVal = 0; - dsc.op2.SetIconFlag(GTF_EMPTY); - return dsc; - } - + // Create an exact-type or sub-type assertion: objVN is (exactly of | subtype of) typeHndVN static AssertionDsc CreateSubtype(Compiler* comp, ValueNum objVN, ValueNum typeHndVN, bool exact) { assert((objVN != ValueNumStore::NoVN) && comp->vnStore->IsVNTypeHandle(typeHndVN)); @@ -8216,6 +8142,7 @@ class Compiler return dsc; } + // Create a no-throw bounds check assertion: idxVN u< lenVN static AssertionDsc CreateNoThrowArrBnd(Compiler* comp, ValueNum idxVN, ValueNum lenVN) { assert(idxVN != ValueNumStore::NoVN); @@ -8228,6 +8155,63 @@ class Compiler dsc.op1.bnd.vnLen = lenVN; return dsc; } + + // Create "i < bnd +/- k != 0" or just "i < bnd != 0" assertion + static AssertionDsc CreateCompareCheckedBoundArith(Compiler* comp, ValueNum relopVN, bool withArith) + { + assert(relopVN != ValueNumStore::NoVN); + + if (withArith) + { + assert(comp->vnStore->IsVNCompareCheckedBoundArith(relopVN)); + } + else + { + assert(comp->vnStore->IsVNCompareCheckedBound(relopVN)); + } + + AssertionDsc dsc = {}; + dsc.assertionKind = OAK_NOT_EQUAL; + // TODO-Cleanup: Rename O1K_BOUND_OPER_BND and O1K_BOUND_LOOP_BND to something more meaningful + // Also, consider removing O1K_BOUND_LOOP_BND entirely and use O1K_BOUND_OPER_BND for both cases. + // O1K_BOUND_LOOP_BND is basically "i < bnd +/- 0 != 0". Unless it regresses TP. + dsc.op1.kind = withArith ? O1K_BOUND_OPER_BND : O1K_BOUND_LOOP_BND; + dsc.op1.vn = relopVN; + dsc.op2.kind = O2K_CONST_INT; + dsc.op2.vn = comp->vnStore->VNZeroForType(TYP_INT); + dsc.op2.u1.iconVal = 0; + dsc.op2.SetIconFlag(GTF_EMPTY); + return dsc; + } + + // Create "i < constant" or "i u< constant" assertion + // TODO-Cleanup: Rename it as it's not necessarily a loop bound + static AssertionDsc CreateConstantLoopBound(Compiler* comp, ValueNum relopVN, bool isUnsigned) + { + assert(relopVN != ValueNumStore::NoVN); + if (isUnsigned) + { + assert(comp->vnStore->IsVNConstantBoundUnsigned(relopVN)); + } + else + { + assert(comp->vnStore->IsVNConstantBound(relopVN)); + } + + // We can guess the signedness of the loop bound from the VN and remove + // O1K_CONSTANT_LOOP_BND_UN entirely. However, it improves the TP a bit since we don't + // have to call GetVNFunc for each assertion during assertion matching. + + AssertionDsc dsc = {}; + dsc.assertionKind = OAK_NOT_EQUAL; + dsc.op1.kind = isUnsigned ? O1K_CONSTANT_LOOP_BND_UN : O1K_CONSTANT_LOOP_BND; + dsc.op1.vn = relopVN; + dsc.op2.kind = O2K_CONST_INT; + dsc.op2.vn = comp->vnStore->VNZeroForType(TYP_INT); + dsc.op2.u1.iconVal = 0; + dsc.op2.SetIconFlag(GTF_EMPTY); + return dsc; + } }; protected: @@ -8305,8 +8289,6 @@ class Compiler // Assertion creation functions. AssertionIndex optCreateAssertion(GenTree* op1, GenTree* op2, bool equals); - AssertionIndex optFinalizeCreatingAssertion(AssertionDsc* assertion); - bool optTryExtractSubrangeAssertion(GenTree* source, IntegralRange* pRange); void optCreateComplementaryAssertion(AssertionIndex assertionIndex, GenTree* op1, GenTree* op2); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 96d72b392793e0..1fc21ea7913e93 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -12791,13 +12791,11 @@ void Compiler::fgAssertionGen(GenTree* tree) ssize_t iconVal = assertion->op2.u1.iconVal; if ((iconVal == 0) || (iconVal == 1)) { - AssertionDsc extraAssertion = {OAK_SUBRANGE}; - extraAssertion.op1.kind = O1K_LCLVAR; - extraAssertion.op1.lclNum = assertion->op1.lclNum; - extraAssertion.op2.kind = O2K_SUBRANGE; - extraAssertion.op2.u2 = IntegralRange(SymbolicIntegerValue::Zero, SymbolicIntegerValue::One); - - AssertionIndex extraIndex = optFinalizeCreatingAssertion(&extraAssertion); + AssertionDsc extraAssertion = + AssertionDsc::CreateSubrange(this, assertion->op1.lclNum, + IntegralRange(SymbolicIntegerValue::Zero, + SymbolicIntegerValue::One)); + AssertionIndex extraIndex = optAddAssertion(&extraAssertion); if (extraIndex != NO_ASSERTION_INDEX) { unsigned const bvIndex = extraIndex - 1; From db19d8bd2c5e5a22f860501b8eb8e6bcde354b42 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 31 Jan 2026 21:15:48 +0100 Subject: [PATCH 05/17] fix bug --- src/coreclr/jit/assertionprop.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index d752fa0902e362..f3755520af0235 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1349,7 +1349,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ } // Try and see if we can make a subrange assertion. - if (equals && varTypeIsIntegral(op2)) + if (optLocalAssertionProp && equals && varTypeIsIntegral(op2)) { IntegralRange nodeRange = IntegralRange::ForNode(op2, this); IntegralRange typeRange = IntegralRange::ForType(genActualType(op2)); From 4256d3761664ad41d46ee4032953f27b85ec71ff Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Sat, 31 Jan 2026 21:16:53 +0100 Subject: [PATCH 06/17] Update src/coreclr/jit/compiler.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/jit/compiler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index eb134034586323..a63d0590fd6a1d 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8051,7 +8051,7 @@ class Compiler } else { - static_assert("Unexpected type for cns"); + static_assert(!std::is_same_v, "Unexpected type for cns"); } return dsc; } From 928aec604dfcd6325d3aaeb75918eef712925d37 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 31 Jan 2026 21:18:37 +0100 Subject: [PATCH 07/17] fix comments --- src/coreclr/jit/assertionprop.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index f3755520af0235..d0d39293bc539a 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1124,12 +1124,10 @@ ssize_t Compiler::optCastConstantSmall(ssize_t iconVal, var_types smallType) // optCreateAssertion: Create an (op1 assertionKind op2) assertion. // // Arguments: -// op1 - the first assertion operand -// op2 - the second assertion operand -// assertionKind - the assertion kind -// helperCallArgs - when true this indicates that the assertion operands -// are the arguments of a type cast helper call such as -// CORINFO_HELP_ISINSTANCEOFCLASS +// op1 - the first assertion operand +// op2 - the second assertion operand +// equals - the assertion kind (equals / not equals) + // Return Value: // The new assertion index or NO_ASSERTION_INDEX if a new assertion // was not created. @@ -1785,9 +1783,9 @@ void Compiler::optCreateComplementaryAssertion(AssertionIndex assertionIndex, Ge // optCreateJtrueAssertions: Create assertions about a JTRUE's relop operands. // // Arguments: -// op1 - the first assertion operand -// op2 - the second assertion operand -// assertionKind - the assertion kind +// op1 - the first assertion operand +// op2 - the second assertion operand +// equals - the assertion kind (equals / not equals) // // Return Value: // The new assertion index or NO_ASSERTION_INDEX if a new assertion From 105574dd83dac94b4f2a2066b99960eab3de609e Mon Sep 17 00:00:00 2001 From: Egor Bogatov Date: Sat, 31 Jan 2026 21:18:57 +0100 Subject: [PATCH 08/17] Update src/coreclr/jit/compiler.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/jit/compiler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index a63d0590fd6a1d..655caf2eea7169 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8109,7 +8109,7 @@ class Compiler static AssertionDsc CreateInt32ConstantVNAssertion(Compiler* comp, ValueNum op1VN, ValueNum op2VN, bool equals) { assert(op1VN != ValueNumStore::NoVN); - assert(op1VN != ValueNumStore::NoVN); + assert(op2VN != ValueNumStore::NoVN); assert(comp->vnStore->IsVNInt32Constant(op2VN)); assert(!comp->vnStore->IsVNHandle(op2VN)); assert(!comp->optLocalAssertionProp); From de5a071e1cad6c027668e0737eaac1cd6200e043 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 31 Jan 2026 21:57:41 +0100 Subject: [PATCH 09/17] replace AssertionDsc* with const AssertionDsc& --- src/coreclr/jit/assertionprop.cpp | 322 +++++++++++++++--------------- src/coreclr/jit/compiler.h | 83 ++++---- src/coreclr/jit/morph.cpp | 12 +- src/coreclr/jit/rangecheck.cpp | 2 +- 4 files changed, 207 insertions(+), 212 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index d0d39293bc539a..e41c297e16e27f 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -752,34 +752,34 @@ void Compiler::optAssertionInit(bool isLocalProp) } #ifdef DEBUG -void Compiler::optPrintAssertion(AssertionDsc* curAssertion, AssertionIndex assertionIndex /* = 0 */) +void Compiler::optPrintAssertion(const AssertionDsc& curAssertion, AssertionIndex assertionIndex /* = 0 */) { - if (curAssertion->op1.kind == O1K_EXACT_TYPE) + if (curAssertion.op1.kind == O1K_EXACT_TYPE) { printf("Type "); } - else if (curAssertion->op1.kind == O1K_ARR_BND) + else if (curAssertion.op1.kind == O1K_ARR_BND) { printf("ArrBnds "); } - else if (curAssertion->op1.kind == O1K_VN) + else if (curAssertion.op1.kind == O1K_VN) { printf("Vn "); } - else if (curAssertion->op1.kind == O1K_SUBTYPE) + else if (curAssertion.op1.kind == O1K_SUBTYPE) { printf("Subtype "); } - else if (curAssertion->op2.kind == O2K_LCLVAR_COPY) + else if (curAssertion.op2.kind == O2K_LCLVAR_COPY) { printf("Copy "); } - else if ((curAssertion->op2.kind == O2K_CONST_INT) || (curAssertion->op2.kind == O2K_CONST_DOUBLE) || - (curAssertion->op2.kind == O2K_ZEROOBJ)) + else if ((curAssertion.op2.kind == O2K_CONST_INT) || (curAssertion.op2.kind == O2K_CONST_DOUBLE) || + (curAssertion.op2.kind == O2K_ZEROOBJ)) { printf("Constant "); } - else if (curAssertion->op2.kind == O2K_SUBRANGE) + else if (curAssertion.op2.kind == O2K_SUBRANGE) { printf("Subrange "); } @@ -791,77 +791,77 @@ void Compiler::optPrintAssertion(AssertionDsc* curAssertion, AssertionIndex asse if (!optLocalAssertionProp) { - printf("(" FMT_VN "," FMT_VN ") ", curAssertion->op1.vn, curAssertion->op2.vn); + printf("(" FMT_VN "," FMT_VN ") ", curAssertion.op1.vn, curAssertion.op2.vn); } - if (curAssertion->op1.kind == O1K_LCLVAR) + if (curAssertion.op1.kind == O1K_LCLVAR) { if (!optLocalAssertionProp) { printf("LCLVAR"); - vnStore->vnDump(this, curAssertion->op1.vn); + vnStore->vnDump(this, curAssertion.op1.vn); } else { - printf("V%02u", curAssertion->op1.lclNum); + printf("V%02u", curAssertion.op1.lclNum); } } - else if (curAssertion->op1.kind == O1K_EXACT_TYPE) + else if (curAssertion.op1.kind == O1K_EXACT_TYPE) { printf("Exact_Type"); - vnStore->vnDump(this, curAssertion->op1.vn); + vnStore->vnDump(this, curAssertion.op1.vn); } - else if (curAssertion->op1.kind == O1K_SUBTYPE) + else if (curAssertion.op1.kind == O1K_SUBTYPE) { printf("Sub_Type"); - vnStore->vnDump(this, curAssertion->op1.vn); + vnStore->vnDump(this, curAssertion.op1.vn); } - else if (curAssertion->op1.kind == O1K_ARR_BND) + else if (curAssertion.op1.kind == O1K_ARR_BND) { - printf("[idx: " FMT_VN, curAssertion->op1.bnd.vnIdx); - vnStore->vnDump(this, curAssertion->op1.bnd.vnIdx); - printf("; len: " FMT_VN, curAssertion->op1.bnd.vnLen); - vnStore->vnDump(this, curAssertion->op1.bnd.vnLen); + printf("[idx: " FMT_VN, curAssertion.op1.bnd.vnIdx); + vnStore->vnDump(this, curAssertion.op1.bnd.vnIdx); + printf("; len: " FMT_VN, curAssertion.op1.bnd.vnLen); + vnStore->vnDump(this, curAssertion.op1.bnd.vnLen); printf("]"); } - else if (curAssertion->op1.kind == O1K_VN) + else if (curAssertion.op1.kind == O1K_VN) { - printf("[vn: " FMT_VN, curAssertion->op1.vn); - vnStore->vnDump(this, curAssertion->op1.vn); + printf("[vn: " FMT_VN, curAssertion.op1.vn); + vnStore->vnDump(this, curAssertion.op1.vn); printf("]"); } - else if (curAssertion->op1.kind == O1K_BOUND_OPER_BND) + else if (curAssertion.op1.kind == O1K_BOUND_OPER_BND) { printf("Oper_Bnd"); - vnStore->vnDump(this, curAssertion->op1.vn); + vnStore->vnDump(this, curAssertion.op1.vn); } - else if (curAssertion->op1.kind == O1K_BOUND_LOOP_BND) + else if (curAssertion.op1.kind == O1K_BOUND_LOOP_BND) { printf("Loop_Bnd"); - vnStore->vnDump(this, curAssertion->op1.vn); + vnStore->vnDump(this, curAssertion.op1.vn); } - else if (curAssertion->op1.kind == O1K_CONSTANT_LOOP_BND) + else if (curAssertion.op1.kind == O1K_CONSTANT_LOOP_BND) { printf("Const_Loop_Bnd"); - vnStore->vnDump(this, curAssertion->op1.vn); + vnStore->vnDump(this, curAssertion.op1.vn); } - else if (curAssertion->op1.kind == O1K_CONSTANT_LOOP_BND_UN) + else if (curAssertion.op1.kind == O1K_CONSTANT_LOOP_BND_UN) { printf("Const_Loop_Bnd_Un"); - vnStore->vnDump(this, curAssertion->op1.vn); + vnStore->vnDump(this, curAssertion.op1.vn); } else { printf("?op1.kind?"); } - if (curAssertion->assertionKind == OAK_SUBRANGE) + if (curAssertion.assertionKind == OAK_SUBRANGE) { printf(" in "); } - else if (curAssertion->assertionKind == OAK_EQUAL) + else if (curAssertion.assertionKind == OAK_EQUAL) { - if (curAssertion->op1.kind == O1K_LCLVAR) + if (curAssertion.op1.kind == O1K_LCLVAR) { printf(" == "); } @@ -870,13 +870,13 @@ void Compiler::optPrintAssertion(AssertionDsc* curAssertion, AssertionIndex asse printf(" is "); } } - else if (curAssertion->assertionKind == OAK_NO_THROW) + else if (curAssertion.assertionKind == OAK_NO_THROW) { printf(" in range "); } - else if (curAssertion->assertionKind == OAK_NOT_EQUAL) + else if (curAssertion.assertionKind == OAK_NOT_EQUAL) { - if (curAssertion->op1.kind == O1K_LCLVAR) + if (curAssertion.op1.kind == O1K_LCLVAR) { printf(" != "); } @@ -890,18 +890,18 @@ void Compiler::optPrintAssertion(AssertionDsc* curAssertion, AssertionIndex asse printf(" ?assertionKind? "); } - if (curAssertion->op1.kind != O1K_ARR_BND) + if (curAssertion.op1.kind != O1K_ARR_BND) { - switch (curAssertion->op2.kind) + switch (curAssertion.op2.kind) { case O2K_LCLVAR_COPY: - printf("V%02u", curAssertion->op2.lclNum); + printf("V%02u", curAssertion.op2.lclNum); break; case O2K_CONST_INT: - if (curAssertion->op1.kind == O1K_EXACT_TYPE) + if (curAssertion.op1.kind == O1K_EXACT_TYPE) { - ssize_t iconVal = curAssertion->op2.u1.iconVal; + ssize_t iconVal = curAssertion.op2.u1.iconVal; if (IsAot()) { printf("Exact Type MT(0x%p)", dspPtr(iconVal)); @@ -913,15 +913,15 @@ void Compiler::optPrintAssertion(AssertionDsc* curAssertion, AssertionIndex asse } // We might want to assert: - // assert(curAssertion->op2.HasIconFlag()); + // assert(curAssertion.op2.HasIconFlag()); // However, if we run CSE with shared constant mode, we may end up with an expression instead // of the original handle value. If we then use JitOptRepeat to re-build value numbers, we lose // knowledge that the constant was ever a handle, as the expression creating the original value // was not (and can't be) assigned a handle flag. } - else if (curAssertion->op1.kind == O1K_SUBTYPE) + else if (curAssertion.op1.kind == O1K_SUBTYPE) { - ssize_t iconVal = curAssertion->op2.u1.iconVal; + ssize_t iconVal = curAssertion.op2.u1.iconVal; if (IsAot()) { printf("MT(0x%p)", dspPtr(iconVal)); @@ -930,53 +930,53 @@ void Compiler::optPrintAssertion(AssertionDsc* curAssertion, AssertionIndex asse { printf("MT(0x%p %s)", dspPtr(iconVal), eeGetClassName((CORINFO_CLASS_HANDLE)iconVal)); } - assert(curAssertion->op2.HasIconFlag()); + assert(curAssertion.op2.HasIconFlag()); } - else if ((curAssertion->op1.kind == O1K_BOUND_OPER_BND) || - (curAssertion->op1.kind == O1K_BOUND_LOOP_BND) || - (curAssertion->op1.kind == O1K_CONSTANT_LOOP_BND) || - (curAssertion->op1.kind == O1K_CONSTANT_LOOP_BND_UN)) + else if ((curAssertion.op1.kind == O1K_BOUND_OPER_BND) || + (curAssertion.op1.kind == O1K_BOUND_LOOP_BND) || + (curAssertion.op1.kind == O1K_CONSTANT_LOOP_BND) || + (curAssertion.op1.kind == O1K_CONSTANT_LOOP_BND_UN)) { assert(!optLocalAssertionProp); - vnStore->vnDump(this, curAssertion->op2.vn); + vnStore->vnDump(this, curAssertion.op2.vn); } else { - var_types op1Type = !optLocalAssertionProp ? vnStore->TypeOfVN(curAssertion->op1.vn) - : lvaGetRealType(curAssertion->op1.lclNum); + var_types op1Type = !optLocalAssertionProp ? vnStore->TypeOfVN(curAssertion.op1.vn) + : lvaGetRealType(curAssertion.op1.lclNum); if (op1Type == TYP_REF) { - if (curAssertion->op2.u1.iconVal == 0) + if (curAssertion.op2.u1.iconVal == 0) { printf("null"); } else { - printf("[%08p]", dspPtr(curAssertion->op2.u1.iconVal)); + printf("[%08p]", dspPtr(curAssertion.op2.u1.iconVal)); } } else { - if (curAssertion->op2.HasIconFlag()) + if (curAssertion.op2.HasIconFlag()) { - printf("[%08p]", dspPtr(curAssertion->op2.u1.iconVal)); + printf("[%08p]", dspPtr(curAssertion.op2.u1.iconVal)); } else { - printf("%d", curAssertion->op2.u1.iconVal); + printf("%d", curAssertion.op2.u1.iconVal); } } } break; case O2K_CONST_DOUBLE: - if (FloatingPointUtils::isNegativeZero(curAssertion->op2.dconVal)) + if (FloatingPointUtils::isNegativeZero(curAssertion.op2.dconVal)) { printf("-0.00000"); } else { - printf("%#lg", curAssertion->op2.dconVal); + printf("%#lg", curAssertion.op2.dconVal); } break; @@ -985,7 +985,7 @@ void Compiler::optPrintAssertion(AssertionDsc* curAssertion, AssertionIndex asse break; case O2K_SUBRANGE: - IntegralRange::Print(curAssertion->op2.u2); + IntegralRange::Print(curAssertion.op2.u2); break; default: @@ -1064,14 +1064,14 @@ void Compiler::optDumpAssertionIndices(ASSERT_TP assertions, const char* footer * is NO_ASSERTION_INDEX and "optAssertionCount" is the last valid index. * */ -Compiler::AssertionDsc* Compiler::optGetAssertion(AssertionIndex assertIndex) +Compiler::AssertionDsc* Compiler::optGetAssertion(AssertionIndex assertIndex) const { assert(NO_ASSERTION_INDEX == 0); assert(assertIndex != NO_ASSERTION_INDEX); assert(assertIndex <= optAssertionCount); AssertionDsc* assertion = &optAssertionTabPrivate[assertIndex - 1]; #ifdef DEBUG - optDebugCheckAssertion(assertion); + optDebugCheckAssertion(*assertion); #endif return assertion; @@ -1172,7 +1172,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ if (optLocalAssertionProp) { AssertionDsc assertion = AssertionDsc::CreateLclNonNullAssertion(this, op1->AsLclVar()->GetLclNum()); - return optAddAssertion(&assertion); + return optAddAssertion(assertion); } ValueNum op1VN = optConservativeNormalVN(op1); @@ -1181,7 +1181,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ return NO_ASSERTION_INDEX; } AssertionDsc assertion = AssertionDsc::CreateVNNonNullAssertion(this, op1VN); - return optAddAssertion(&assertion); + return optAddAssertion(assertion); } } // @@ -1227,7 +1227,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ AssertionDsc dsc = AssertionDsc::CreateConstantLclvarAssertion(this, lclNum, op1VN, op2->AsDblCon()->DconValue(), op2VN, equals); - return optAddAssertion(&dsc); + return optAddAssertion(dsc); } case GT_CNS_INT: @@ -1244,7 +1244,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ AssertionDsc dsc = AssertionDsc::CreateConstantLclvarAssertion(this, lclNum, op1VN, 0, op2VN, equals); dsc.op2.kind = O2K_ZEROOBJ; - return optAddAssertion(&dsc); + return optAddAssertion(dsc); } ssize_t iconVal = op2->AsIntCon()->IconValue(); @@ -1269,7 +1269,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ AssertionDsc dsc = AssertionDsc::CreateConstantLclvarAssertion(this, lclNum, op1VN, iconVal, op2VN, equals); dsc.op2.SetIconFlag(op2->GetIconHandleFlag(), op2->AsIntCon()->gtFieldSeq); - return optAddAssertion(&dsc); + return optAddAssertion(dsc); } case GT_LCL_VAR: @@ -1325,7 +1325,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ // Ok everything has been set and the assertion looks good AssertionDsc assertion = AssertionDsc::CreateLclvarCopy(this, lclNum, lclNum2, equals); - return optAddAssertion(&assertion); + return optAddAssertion(assertion); } case GT_CALL: @@ -1336,7 +1336,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ if (call->IsHelperCall() && s_helperCallProperties.NonNullReturn(call->GetHelperNum())) { AssertionDsc assertion = AssertionDsc::CreateLclNonNullAssertion(this, lclNum); - return optAddAssertion(&assertion); + return optAddAssertion(assertion); } } break; @@ -1356,7 +1356,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ if (!typeRange.Equals(nodeRange)) { AssertionDsc assertion = AssertionDsc::CreateSubrange(this, lclNum, nodeRange); - return optAddAssertion(&assertion); + return optAddAssertion(assertion); } } } @@ -1375,7 +1375,7 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ !vnStore->IsVNHandle(op2VN)) { AssertionDsc assertion = AssertionDsc::CreateInt32ConstantVNAssertion(this, op1VN, op2VN, equals); - return optAddAssertion(&assertion); + return optAddAssertion(assertion); } } } @@ -1437,7 +1437,7 @@ bool Compiler::optIsTreeKnownIntValue(bool vnBased, GenTree* tree, ssize_t* pCon * Print the assertions related to a VN for all VNs. * */ -void Compiler::optPrintVnAssertionMapping() +void Compiler::optPrintVnAssertionMapping() const { printf("\nVN Assertion Mapping\n"); printf("---------------------\n"); @@ -1454,7 +1454,7 @@ void Compiler::optPrintVnAssertionMapping() * about that VN. Given "assertions" about a "vn" add it to the previously * mapped assertions about that "vn." */ -void Compiler::optAddVnAssertionMapping(ValueNum vn, AssertionIndex index) +void Compiler::optAddVnAssertionMapping(ValueNum vn, AssertionIndex index) const { ASSERT_TP* cur = optValueNumToAsserts->LookupPointer(vn); if (cur == nullptr) @@ -1471,7 +1471,7 @@ void Compiler::optAddVnAssertionMapping(ValueNum vn, AssertionIndex index) * Statically if we know that this assertion's VN involves a NaN don't bother * wasting an assertion table slot. */ -bool Compiler::optAssertionVnInvolvesNan(AssertionDsc* assertion) +bool Compiler::optAssertionVnInvolvesNan(const AssertionDsc& assertion) const { if (optLocalAssertionProp) { @@ -1479,7 +1479,7 @@ bool Compiler::optAssertionVnInvolvesNan(AssertionDsc* assertion) } static const int SZ = 2; - ValueNum vns[SZ] = {assertion->op1.vn, assertion->op2.vn}; + ValueNum vns[SZ] = {assertion.op1.vn, assertion.op2.vn}; for (int i = 0; i < SZ; ++i) { if (vnStore->IsVNConstant(vns[i])) @@ -1505,9 +1505,9 @@ bool Compiler::optAssertionVnInvolvesNan(AssertionDsc* assertion) * we use to refer to this element. * If we need to add to the table and the table is full return the value zero */ -AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) +AssertionIndex Compiler::optAddAssertion(const AssertionDsc& newAssertion) { - noway_assert(newAssertion->assertionKind != OAK_INVALID); + noway_assert(newAssertion.assertionKind != OAK_INVALID); // Even though the propagation step takes care of NaN, just a check // to make sure there is no slot involving a NaN. @@ -1520,7 +1520,7 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) if (!optLocalAssertionProp) { // Ignore VN-based assertions with NoVN - switch (newAssertion->op1.kind) + switch (newAssertion.op1.kind) { case O1K_LCLVAR: case O1K_VN: @@ -1530,14 +1530,14 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) case O1K_CONSTANT_LOOP_BND_UN: case O1K_EXACT_TYPE: case O1K_SUBTYPE: - if (newAssertion->op1.vn == ValueNumStore::NoVN) + if (newAssertion.op1.vn == ValueNumStore::NoVN) { return NO_ASSERTION_INDEX; } break; case O1K_ARR_BND: - if ((newAssertion->op1.bnd.vnIdx == ValueNumStore::NoVN) || - (newAssertion->op1.bnd.vnLen == ValueNumStore::NoVN)) + if ((newAssertion.op1.bnd.vnIdx == ValueNumStore::NoVN) || + (newAssertion.op1.bnd.vnLen == ValueNumStore::NoVN)) { return NO_ASSERTION_INDEX; } @@ -1556,9 +1556,9 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) // if (optLocalAssertionProp) { - assert(newAssertion->op1.kind == O1K_LCLVAR); + assert(newAssertion.op1.kind == O1K_LCLVAR); - unsigned lclNum = newAssertion->op1.lclNum; + unsigned lclNum = newAssertion.op1.lclNum; BitVecOps::Iter iter(apTraits, GetAssertionDep(lclNum)); unsigned bvIndex = 0; while (iter.NextElem(&bvIndex)) @@ -1594,7 +1594,7 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) return NO_ASSERTION_INDEX; } - optAssertionTabPrivate[optAssertionCount] = *newAssertion; + optAssertionTabPrivate[optAssertionCount] = newAssertion; optAssertionCount++; #ifdef DEBUG @@ -1608,33 +1608,33 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) #endif // DEBUG // Track the short-circuit criteria - optCanPropLclVar |= newAssertion->CanPropLclVar(); - optCanPropEqual |= newAssertion->CanPropEqualOrNotEqual(); - optCanPropNonNull |= newAssertion->CanPropNonNull(); - optCanPropSubRange |= newAssertion->CanPropSubRange(); - optCanPropBndsChk |= newAssertion->CanPropBndsCheck(); + optCanPropLclVar |= newAssertion.CanPropLclVar(); + optCanPropEqual |= newAssertion.CanPropEqualOrNotEqual(); + optCanPropNonNull |= newAssertion.CanPropNonNull(); + optCanPropSubRange |= newAssertion.CanPropSubRange(); + optCanPropBndsChk |= newAssertion.CanPropBndsCheck(); // Assertion mask bits are [index + 1]. if (optLocalAssertionProp) { - assert(newAssertion->op1.kind == O1K_LCLVAR); + assert(newAssertion.op1.kind == O1K_LCLVAR); // Mark the variables this index depends on - unsigned lclNum = newAssertion->op1.lclNum; + unsigned lclNum = newAssertion.op1.lclNum; BitVecOps::AddElemD(apTraits, GetAssertionDep(lclNum), optAssertionCount - 1); - if (newAssertion->op2.kind == O2K_LCLVAR_COPY) + if (newAssertion.op2.kind == O2K_LCLVAR_COPY) { - lclNum = newAssertion->op2.lclNum; + lclNum = newAssertion.op2.lclNum; BitVecOps::AddElemD(apTraits, GetAssertionDep(lclNum), optAssertionCount - 1); } } else // If global assertion prop, then add it to the dependents map. { - optAddVnAssertionMapping(newAssertion->op1.vn, optAssertionCount); - if (newAssertion->op2.kind == O2K_LCLVAR_COPY) + optAddVnAssertionMapping(newAssertion.op1.vn, optAssertionCount); + if (newAssertion.op2.kind == O2K_LCLVAR_COPY) { - optAddVnAssertionMapping(newAssertion->op2.vn, optAssertionCount); + optAddVnAssertionMapping(newAssertion.op2.vn, optAssertionCount); } } @@ -1645,19 +1645,19 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) } #ifdef DEBUG -void Compiler::optDebugCheckAssertion(AssertionDsc* assertion) +void Compiler::optDebugCheckAssertion(const AssertionDsc& assertion) const { - assert(assertion->assertionKind < OAK_COUNT); - assert(assertion->op1.kind < O1K_COUNT); - assert(assertion->op2.kind < O2K_COUNT); + assert(assertion.assertionKind < OAK_COUNT); + assert(assertion.op1.kind < O1K_COUNT); + assert(assertion.op2.kind < O2K_COUNT); // It would be good to check that op1.vn and op2.vn are valid value numbers. - switch (assertion->op1.kind) + switch (assertion.op1.kind) { case O1K_ARR_BND: // It would be good to check that bnd.vnIdx and bnd.vnLen are valid value numbers. assert(!optLocalAssertionProp); - assert(assertion->assertionKind == OAK_NO_THROW); + assert(assertion.assertionKind == OAK_NO_THROW); break; case O1K_EXACT_TYPE: case O1K_SUBTYPE: @@ -1671,7 +1671,7 @@ void Compiler::optDebugCheckAssertion(AssertionDsc* assertion) default: break; } - switch (assertion->op2.kind) + switch (assertion.op2.kind) { case O2K_SUBRANGE: case O2K_LCLVAR_COPY: @@ -1681,14 +1681,14 @@ void Compiler::optDebugCheckAssertion(AssertionDsc* assertion) case O2K_ZEROOBJ: { // We only make these assertion for stores (not control flow). - assert(assertion->assertionKind == OAK_EQUAL); + assert(assertion.assertionKind == OAK_EQUAL); // We use "optLocalAssertionIsEqualOrNotEqual" to find these. - assert(assertion->op2.u1.iconVal == 0); + assert(assertion.op2.u1.iconVal == 0); } break; default: - // for all other 'assertion->op2.kind' values we don't check anything + // for all other 'assertion.op2.kind' values we don't check anything break; } } @@ -1707,7 +1707,7 @@ void Compiler::optDebugCheckAssertions(AssertionIndex index) for (AssertionIndex ind = start; ind <= end; ++ind) { AssertionDsc* assertion = optGetAssertion(ind); - optDebugCheckAssertion(assertion); + optDebugCheckAssertion(*assertion); } } #endif @@ -1739,7 +1739,7 @@ void Compiler::optCreateComplementaryAssertion(AssertionIndex assertionIndex, Ge { AssertionDsc dsc = candidateAssertion; dsc.ReverseEquality(); - optAddAssertion(&dsc); + optAddAssertion(dsc); return; } @@ -1834,7 +1834,7 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) if (vnStore->IsVNCompareCheckedBoundArith(relopVN)) { AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBoundArith(this, relopVN, /*withArith*/ true); - AssertionIndex index = optAddAssertion(&dsc); + AssertionIndex index = optAddAssertion(dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; } @@ -1844,7 +1844,7 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) else if (vnStore->IsVNCompareCheckedBound(relopVN)) { AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBoundArith(this, relopVN, /*withArith*/ false); - AssertionIndex index = optAddAssertion(&dsc); + AssertionIndex index = optAddAssertion(dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; } @@ -1856,7 +1856,7 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) ValueNum lenVN = vnStore->VNNormalValue(unsignedCompareBnd.vnBound); AssertionDsc dsc = AssertionDsc::CreateNoThrowArrBnd(this, idxVN, lenVN); - AssertionIndex index = optAddAssertion(&dsc); + AssertionIndex index = optAddAssertion(dsc); if (unsignedCompareBnd.cmpOper == VNF_GE_UN) { // By default JTRUE generated assertions hold on the "jump" edge. We have i >= bnd but we're really @@ -1871,14 +1871,14 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) else if (vnStore->IsVNConstantBound(relopVN)) { AssertionDsc dsc = AssertionDsc::CreateConstantLoopBound(this, relopVN, /*isUnsigned*/ false); - AssertionIndex index = optAddAssertion(&dsc); + AssertionIndex index = optAddAssertion(dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; } else if (vnStore->IsVNConstantBoundUnsigned(relopVN)) { AssertionDsc dsc = AssertionDsc::CreateConstantLoopBound(this, relopVN, /*isUnsigned*/ true); - AssertionIndex index = optAddAssertion(&dsc); + AssertionIndex index = optAddAssertion(dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; } @@ -1948,7 +1948,7 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) if ((objVN != ValueNumStore::NoVN) && vnStore->IsVNTypeHandle(typeHndVN)) { AssertionDsc dsc = AssertionDsc::CreateSubtype(this, objVN, typeHndVN, /*exact*/ true); - AssertionIndex index = optAddAssertion(&dsc); + AssertionIndex index = optAddAssertion(dsc); // We don't need to create a complementary assertion here. We're only interested // in the assertion that the object is of a certain type. The opposite assertion @@ -2060,7 +2060,7 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) if ((objVN != ValueNumStore::NoVN) && vnStore->IsVNTypeHandle(typeHndVN)) { AssertionDsc dsc = AssertionDsc::CreateSubtype(this, objVN, typeHndVN, /*exact*/ false); - AssertionIndex index = optAddAssertion(&dsc); + AssertionIndex index = optAddAssertion(dsc); // We don't need to create a complementary assertion here. We're only interested // in the assertion that the object is of a certain type. The opposite assertion @@ -2149,8 +2149,7 @@ void Compiler::optAssertionGen(GenTree* tree) } else { - AssertionDsc assertion = AssertionDsc::CreateNoThrowArrBnd(this, idxVN, lenVN); - assertionInfo = optAddAssertion(&assertion); + assertionInfo = optAddAssertion(AssertionDsc::CreateNoThrowArrBnd(this, idxVN, lenVN)); } } break; @@ -2239,7 +2238,7 @@ AssertionIndex Compiler::optFindComplementary(AssertionIndex assertIndex) { // Make sure assertion kinds are complementary and op1, op2 kinds match. AssertionDsc* curAssertion = optGetAssertion(index); - if (curAssertion->Complementary(inputAssertion, !optLocalAssertionProp)) + if (curAssertion->Complementary(*inputAssertion, !optLocalAssertionProp)) { optMapComplementary(assertIndex, index); return index; @@ -3091,7 +3090,7 @@ bool Compiler::optIsProfitableToSubstitute(GenTree* dest, BasicBlock* destBlock, // Notes: // stmt may be nullptr during local assertion prop // -GenTree* Compiler::optConstantAssertionProp(AssertionDsc* curAssertion, +GenTree* Compiler::optConstantAssertionProp(const AssertionDsc& curAssertion, GenTreeLclVarCommon* tree, Statement* stmt DEBUGARG(AssertionIndex index)) { @@ -3111,23 +3110,23 @@ GenTree* Compiler::optConstantAssertionProp(AssertionDsc* curAssertion, // Update 'newTree' with the new value from our table // Typically newTree == tree and we are updating the node in place - switch (curAssertion->op2.kind) + switch (curAssertion.op2.kind) { case O2K_CONST_DOUBLE: // There could be a positive zero and a negative zero, so don't propagate zeroes. - if (curAssertion->op2.dconVal == 0.0) + if (curAssertion.op2.dconVal == 0.0) { return nullptr; } - newTree->BashToConst(curAssertion->op2.dconVal, tree->TypeGet()); + newTree->BashToConst(curAssertion.op2.dconVal, tree->TypeGet()); break; case O2K_CONST_INT: // Don't propagate non-nulll non-static handles if we need to report relocs. - if (opts.compReloc && curAssertion->op2.HasIconFlag() && (curAssertion->op2.u1.iconVal != 0)) + if (opts.compReloc && curAssertion.op2.HasIconFlag() && (curAssertion.op2.u1.iconVal != 0)) { - if (curAssertion->op2.GetIconFlag() != GTF_ICON_STATIC_HDL) + if (curAssertion.op2.GetIconFlag() != GTF_ICON_STATIC_HDL) { return nullptr; } @@ -3139,11 +3138,11 @@ GenTree* Compiler::optConstantAssertionProp(AssertionDsc* curAssertion, // here). assert(tree->TypeGet() == lvaGetDesc(lclNum)->TypeGet()); - if (curAssertion->op2.HasIconFlag()) + if (curAssertion.op2.HasIconFlag()) { // Here we have to allocate a new 'large' node to replace the old one - newTree = gtNewIconHandleNode(curAssertion->op2.u1.iconVal, curAssertion->op2.GetIconFlag(), - curAssertion->op2.u1.fieldSeq); + newTree = gtNewIconHandleNode(curAssertion.op2.u1.iconVal, curAssertion.op2.GetIconFlag(), + curAssertion.op2.u1.fieldSeq); // Make sure we don't retype const gc handles to TYP_I_IMPL // Although, it's possible for e.g. GTF_ICON_STATIC_HDL @@ -3160,7 +3159,7 @@ GenTree* Compiler::optConstantAssertionProp(AssertionDsc* curAssertion, else { assert(varTypeIsIntegralOrI(tree)); - newTree->BashToConst(curAssertion->op2.u1.iconVal, genActualType(tree)); + newTree->BashToConst(curAssertion.op2.u1.iconVal, genActualType(tree)); } break; @@ -3170,11 +3169,11 @@ GenTree* Compiler::optConstantAssertionProp(AssertionDsc* curAssertion, if (!optLocalAssertionProp) { - assert(newTree->OperIsConst()); // We should have a simple Constant node for newTree - assert(vnStore->IsVNConstant(curAssertion->op2.vn)); // The value number stored for op2 should be a valid - // VN representing the constant - newTree->gtVNPair.SetBoth(curAssertion->op2.vn); // Set the ValueNumPair to the constant VN from op2 - // of the assertion + assert(newTree->OperIsConst()); // We should have a simple Constant node for newTree + assert(vnStore->IsVNConstant(curAssertion.op2.vn)); // The value number stored for op2 should be a valid + // VN representing the constant + newTree->gtVNPair.SetBoth(curAssertion.op2.vn); // Set the ValueNumPair to the constant VN from op2 + // of the assertion } #ifdef DEBUG @@ -3240,7 +3239,7 @@ bool Compiler::optZeroObjAssertionProp(GenTree* tree, ASSERT_VALARG_TP assertion AssertionDsc* assertion = optGetAssertion(assertionIndex); JITDUMP("\nAssertion prop in " FMT_BB ":\n", compCurBB->bbNum); - JITDUMPEXEC(optPrintAssertion(assertion, assertionIndex)); + JITDUMPEXEC(optPrintAssertion(*assertion, assertionIndex)); DISPNODE(tree); tree->BashToZeroConst(TYP_INT); @@ -3326,14 +3325,14 @@ bool Compiler::optAssertionProp_LclVarTypeCheck(GenTree* tree, LclVarDsc* lclVar // Notes: // stmt may be nullptr during local assertion prop // -GenTree* Compiler::optCopyAssertionProp(AssertionDsc* curAssertion, +GenTree* Compiler::optCopyAssertionProp(const AssertionDsc& curAssertion, GenTreeLclVarCommon* tree, Statement* stmt DEBUGARG(AssertionIndex index)) { assert(optLocalAssertionProp); - const AssertionDsc::AssertionDscOp1& op1 = curAssertion->op1; - const AssertionDsc::AssertionDscOp2& op2 = curAssertion->op2; + const AssertionDsc::AssertionDscOp1& op1 = curAssertion.op1; + const AssertionDsc::AssertionDscOp2& op2 = curAssertion.op2; noway_assert(op1.lclNum != op2.lclNum); @@ -3366,7 +3365,7 @@ GenTree* Compiler::optCopyAssertionProp(AssertionDsc* curAssertion, } // Make sure we can perform this copy prop. - if (optCopyProp_LclVarScore(lclVarDsc, copyVarDsc, curAssertion->op1.lclNum == lclNum) <= 0) + if (optCopyProp_LclVarScore(lclVarDsc, copyVarDsc, curAssertion.op1.lclNum == lclNum) <= 0) { return nullptr; } @@ -3477,7 +3476,7 @@ GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeL if (optLocalAssertionProp) { // Perform copy assertion prop. - GenTree* newTree = optCopyAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex)); + GenTree* newTree = optCopyAssertionProp(*curAssertion, tree, stmt DEBUGARG(assertionIndex)); if (newTree != nullptr) { return newTree; @@ -3505,7 +3504,7 @@ GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeL // Check lclNum in Local Assertion Prop if (curAssertion->op1.lclNum == lclNum) { - return optConstantAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex)); + return optConstantAssertionProp(*curAssertion, tree, stmt DEBUGARG(assertionIndex)); } } else @@ -3513,7 +3512,7 @@ GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeL // Check VN in Global Assertion Prop if (curAssertion->op1.vn == vnStore->VNConservativeNormalValue(tree->gtVNPair)) { - return optConstantAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex)); + return optConstantAssertionProp(*curAssertion, tree, stmt DEBUGARG(assertionIndex)); } } } @@ -3571,7 +3570,7 @@ GenTree* Compiler::optAssertionProp_LclFld(ASSERT_VALARG_TP assertions, GenTreeL AssertionDsc* const curAssertion = optGetAssertion(assertionIndex); if (curAssertion->CanPropLclVar() && (curAssertion->op2.kind == O2K_LCLVAR_COPY)) { - GenTree* const newTree = optCopyAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex)); + GenTree* const newTree = optCopyAssertionProp(*curAssertion, tree, stmt DEBUGARG(assertionIndex)); if (newTree != nullptr) { return newTree; @@ -3648,7 +3647,7 @@ GenTree* Compiler::optAssertionProp_LocalStore(ASSERT_VALARG_TP assertions, GenT { JITDUMP("[%06u] is assigning a constant zero to a struct field or gc local that is already zero\n", dspTreeID(store)); - JITDUMPEXEC(optPrintAssertion(dstAssertion)); + JITDUMPEXEC(optPrintAssertion(*dstAssertion)); store->gtBashToNOP(); return optAssertionProp_Update(store, store, stmt); @@ -5416,7 +5415,7 @@ void Compiler::optImpliedAssertions(AssertionIndex assertionIndex, ASSERT_TP& ac if ((curAssertion->assertionKind == OAK_EQUAL) && (curAssertion->op1.kind == O1K_LCLVAR) && (curAssertion->op2.kind == O2K_CONST_INT)) { - optImpliedByConstAssertion(curAssertion, activeAssertions); + optImpliedByConstAssertion(*curAssertion, activeAssertions); } } @@ -5513,14 +5512,14 @@ bool Compiler::optCreateJumpTableImpliedAssertions(BasicBlock* switchBb) // Create "X >= value" assertion (both operands are never negative) ValueNum relop = vnStore->VNForFunc(TYP_INT, VNF_GE, opVN, vnStore->VNForIntCon(value)); AssertionDsc dsc = AssertionDsc::CreateConstantLoopBound(this, relop, /*isUnsigned*/ false); - newAssertIdx = optAddAssertion(&dsc); + newAssertIdx = optAddAssertion(dsc); } else { // Create "X u>= value" assertion ValueNum relop = vnStore->VNForFunc(TYP_INT, VNF_GE_UN, opVN, vnStore->VNForIntCon(value)); AssertionDsc dsc = AssertionDsc::CreateConstantLoopBound(this, relop, /*isUnsigned*/ true); - newAssertIdx = optAddAssertion(&dsc); + newAssertIdx = optAddAssertion(dsc); } } else @@ -5532,10 +5531,9 @@ bool Compiler::optCreateJumpTableImpliedAssertions(BasicBlock* switchBb) { // Create "VN == value" assertion. // TODO-Cleanup: Should use O1K_VN instead of O1K_LCLVAR - ValueNum valueVN = vnStore->VNForIntCon(value); - AssertionDsc dsc = - AssertionDsc::CreateConstantLclvarAssertion(this, BAD_VAR_NUM, opVN, value, valueVN, true); - newAssertIdx = optAddAssertion(&dsc); + ValueNum valueVN = vnStore->VNForIntCon(value); + newAssertIdx = optAddAssertion( + AssertionDsc::CreateConstantLclvarAssertion(this, BAD_VAR_NUM, opVN, value, valueVN, true)); } if (newAssertIdx.HasAssertion()) @@ -5668,15 +5666,15 @@ ASSERT_VALRET_TP Compiler::optGetEdgeAssertions(const BasicBlock* block, const B * that are also true */ -void Compiler::optImpliedByConstAssertion(AssertionDsc* constAssertion, ASSERT_TP& result) +void Compiler::optImpliedByConstAssertion(const AssertionDsc& constAssertion, ASSERT_TP& result) { - noway_assert(constAssertion->assertionKind == OAK_EQUAL); - noway_assert(constAssertion->op1.kind == O1K_LCLVAR); - noway_assert(constAssertion->op2.kind == O2K_CONST_INT); + noway_assert(constAssertion.assertionKind == OAK_EQUAL); + noway_assert(constAssertion.op1.kind == O1K_LCLVAR); + noway_assert(constAssertion.op2.kind == O2K_CONST_INT); - ssize_t iconVal = constAssertion->op2.u1.iconVal; + ssize_t iconVal = constAssertion.op2.u1.iconVal; - const ASSERT_TP chkAssertions = optGetVnMappedAssertions(constAssertion->op1.vn); + const ASSERT_TP chkAssertions = optGetVnMappedAssertions(constAssertion.op1.vn); if (chkAssertions == nullptr || BitVecOps::IsEmpty(apTraits, chkAssertions)) { return; @@ -5694,13 +5692,13 @@ void Compiler::optImpliedByConstAssertion(AssertionDsc* constAssertion, ASSERT_T } // The impAssertion must be different from the const assertion. AssertionDsc* impAssertion = optGetAssertion(chkAssertionIndex); - if (impAssertion == constAssertion) + if (impAssertion->Equals(constAssertion, !optLocalAssertionProp)) { continue; } // The impAssertion must be an assertion about the same local var. - if (impAssertion->op1.vn != constAssertion->op1.vn) + if (impAssertion->op1.vn != constAssertion.op1.vn) { continue; } @@ -5732,7 +5730,7 @@ void Compiler::optImpliedByConstAssertion(AssertionDsc* constAssertion, ASSERT_T { AssertionDsc* firstAssertion = optGetAssertion(1); printf("Compiler::optImpliedByConstAssertion: const assertion #%02d implies assertion #%02d\n", - (constAssertion - firstAssertion) + 1, (impAssertion - firstAssertion) + 1); + (&constAssertion - firstAssertion) + 1, (impAssertion - firstAssertion) + 1); } #endif } diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index a63d0590fd6a1d..3701b5dde9edbf 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7812,12 +7812,12 @@ class Compiler IntegralRange u2; }; - bool HasIconFlag() + bool HasIconFlag() const { assert(m_encodedIconFlags <= 0xFF); return m_encodedIconFlags != 0; } - GenTreeFlags GetIconFlag() + GenTreeFlags GetIconFlag() const { // number of trailing zeros in GTF_ICON_HDL_MASK const uint16_t iconMaskTzc = 24; @@ -7836,61 +7836,61 @@ class Compiler } } op2; - bool IsCheckedBoundArithBound() + bool IsCheckedBoundArithBound() const { return ((assertionKind == OAK_EQUAL || assertionKind == OAK_NOT_EQUAL) && op1.kind == O1K_BOUND_OPER_BND); } - bool IsCheckedBoundBound() + bool IsCheckedBoundBound() const { return ((assertionKind == OAK_EQUAL || assertionKind == OAK_NOT_EQUAL) && op1.kind == O1K_BOUND_LOOP_BND); } - bool IsConstantBound() + bool IsConstantBound() const { return ((assertionKind == OAK_EQUAL || assertionKind == OAK_NOT_EQUAL) && (op1.kind == O1K_CONSTANT_LOOP_BND)); } - bool IsConstantBoundUnsigned() + bool IsConstantBoundUnsigned() const { return ((assertionKind == OAK_EQUAL || assertionKind == OAK_NOT_EQUAL) && (op1.kind == O1K_CONSTANT_LOOP_BND_UN)); } - bool IsBoundsCheckNoThrow() + bool IsBoundsCheckNoThrow() const { return ((assertionKind == OAK_NO_THROW) && (op1.kind == O1K_ARR_BND)); } - bool IsCopyAssertion() + bool IsCopyAssertion() const { return ((assertionKind == OAK_EQUAL) && (op1.kind == O1K_LCLVAR) && (op2.kind == O2K_LCLVAR_COPY)); } - bool IsConstantInt32Assertion() + bool IsConstantInt32Assertion() const { return ((assertionKind == OAK_EQUAL) || (assertionKind == OAK_NOT_EQUAL)) && (op2.kind == O2K_CONST_INT) && ((op1.kind == O1K_LCLVAR) || (op1.kind == O1K_VN)); } - bool CanPropLclVar() + bool CanPropLclVar() const { return assertionKind == OAK_EQUAL && op1.kind == O1K_LCLVAR; } - bool CanPropEqualOrNotEqual() + bool CanPropEqualOrNotEqual() const { return assertionKind == OAK_EQUAL || assertionKind == OAK_NOT_EQUAL; } - bool CanPropNonNull() + bool CanPropNonNull() const { return assertionKind == OAK_NOT_EQUAL && op2.vn == ValueNumStore::VNForNull(); } - bool CanPropBndsCheck() + bool CanPropBndsCheck() const { return (op1.kind == O1K_ARR_BND) || (op1.kind == O1K_VN); } - bool CanPropSubRange() + bool CanPropSubRange() const { return assertionKind == OAK_SUBRANGE && op1.kind == O1K_LCLVAR; } @@ -7901,10 +7901,9 @@ class Compiler assertionKind = assertionKind == OAK_EQUAL ? OAK_NOT_EQUAL : OAK_EQUAL; } - static bool SameKind(AssertionDsc* a1, AssertionDsc* a2) + static bool SameKind(const AssertionDsc& a1, const AssertionDsc& a2) { - return a1->assertionKind == a2->assertionKind && a1->op1.kind == a2->op1.kind && - a1->op2.kind == a2->op2.kind; + return a1.assertionKind == a2.assertionKind && a1.op1.kind == a2.op1.kind && a1.op2.kind == a2.op2.kind; } static bool ComplementaryKind(optAssertionKind kind, optAssertionKind kind2) @@ -7920,31 +7919,31 @@ class Compiler return false; } - bool HasSameOp1(AssertionDsc* that, bool vnBased) + bool HasSameOp1(const AssertionDsc& that, bool vnBased) const { - if (op1.kind != that->op1.kind) + if (op1.kind != that.op1.kind) { return false; } else if (op1.kind == O1K_ARR_BND) { assert(vnBased); - return (op1.bnd.vnIdx == that->op1.bnd.vnIdx) && (op1.bnd.vnLen == that->op1.bnd.vnLen); + return (op1.bnd.vnIdx == that.op1.bnd.vnIdx) && (op1.bnd.vnLen == that.op1.bnd.vnLen); } else if (op1.kind == O1K_VN) { assert(vnBased); - return (op1.vn == that->op1.vn); + return (op1.vn == that.op1.vn); } else { - return ((vnBased && (op1.vn == that->op1.vn)) || (!vnBased && (op1.lclNum == that->op1.lclNum))); + return ((vnBased && (op1.vn == that.op1.vn)) || (!vnBased && (op1.lclNum == that.op1.lclNum))); } } - bool HasSameOp2(AssertionDsc* that, bool vnBased) + bool HasSameOp2(const AssertionDsc& that, bool vnBased) const { - if (op2.kind != that->op2.kind) + if (op2.kind != that.op2.kind) { return false; } @@ -7952,20 +7951,20 @@ class Compiler switch (op2.kind) { case O2K_CONST_INT: - return ((op2.u1.iconVal == that->op2.u1.iconVal) && (op2.GetIconFlag() == that->op2.GetIconFlag())); + return ((op2.u1.iconVal == that.op2.u1.iconVal) && (op2.GetIconFlag() == that.op2.GetIconFlag())); case O2K_CONST_DOUBLE: // exact match because of positive and negative zero. - return (memcmp(&op2.dconVal, &that->op2.dconVal, sizeof(double)) == 0); + return (memcmp(&op2.dconVal, &that.op2.dconVal, sizeof(double)) == 0); case O2K_ZEROOBJ: return true; case O2K_LCLVAR_COPY: - return op2.lclNum == that->op2.lclNum; + return op2.lclNum == that.op2.lclNum; case O2K_SUBRANGE: - return op2.u2.Equals(that->op2.u2); + return op2.u2.Equals(that.op2.u2); case O2K_INVALID: // we will return false @@ -7979,15 +7978,15 @@ class Compiler return false; } - bool Complementary(AssertionDsc* that, bool vnBased) + bool Complementary(const AssertionDsc& that, bool vnBased) const { - return ComplementaryKind(assertionKind, that->assertionKind) && HasSameOp1(that, vnBased) && + return ComplementaryKind(assertionKind, that.assertionKind) && HasSameOp1(that, vnBased) && HasSameOp2(that, vnBased); } - bool Equals(AssertionDsc* that, bool vnBased) + bool Equals(const AssertionDsc& that, bool vnBased) const { - if (assertionKind != that->assertionKind) + if (assertionKind != that.assertionKind) { return false; } @@ -8260,7 +8259,7 @@ class Compiler // Assertion prop helpers. ASSERT_TP& GetAssertionDep(unsigned lclNum); - AssertionDsc* optGetAssertion(AssertionIndex assertIndex); + AssertionDsc* optGetAssertion(AssertionIndex assertIndex) const; void optAssertionInit(bool isLocalProp); void optAssertionTraitsInit(AssertionIndex assertionCount); void optAssertionReset(AssertionIndex limit); @@ -8293,11 +8292,11 @@ class Compiler void optCreateComplementaryAssertion(AssertionIndex assertionIndex, GenTree* op1, GenTree* op2); - bool optAssertionVnInvolvesNan(AssertionDsc* assertion); - AssertionIndex optAddAssertion(AssertionDsc* assertion); - void optAddVnAssertionMapping(ValueNum vn, AssertionIndex index); + bool optAssertionVnInvolvesNan(const AssertionDsc& assertion) const; + AssertionIndex optAddAssertion(const AssertionDsc& assertion); + void optAddVnAssertionMapping(ValueNum vn, AssertionIndex index) const; #ifdef DEBUG - void optPrintVnAssertionMapping(); + void optPrintVnAssertionMapping() const; #endif ASSERT_TP optGetVnMappedAssertions(ValueNum vn); @@ -8314,10 +8313,10 @@ class Compiler // Assertion prop for lcl var functions. bool optAssertionProp_LclVarTypeCheck(GenTree* tree, LclVarDsc* lclVarDsc, LclVarDsc* copyVarDsc); - GenTree* optCopyAssertionProp(AssertionDsc* curAssertion, + GenTree* optCopyAssertionProp(const AssertionDsc& curAssertion, GenTreeLclVarCommon* tree, Statement* stmt DEBUGARG(AssertionIndex index)); - GenTree* optConstantAssertionProp(AssertionDsc* curAssertion, + GenTree* optConstantAssertionProp(const AssertionDsc& curAssertion, GenTreeLclVarCommon* tree, Statement* stmt DEBUGARG(AssertionIndex index)); bool optIsProfitableToSubstitute(GenTree* dest, BasicBlock* destBlock, GenTree* destParent, GenTree* value); @@ -8367,13 +8366,13 @@ class Compiler void optImpliedAssertions(AssertionIndex assertionIndex, ASSERT_TP& activeAssertions); void optImpliedByTypeOfAssertions(ASSERT_TP& activeAssertions); bool optCreateJumpTableImpliedAssertions(BasicBlock* switchBb); - void optImpliedByConstAssertion(AssertionDsc* curAssertion, ASSERT_TP& result); + void optImpliedByConstAssertion(const AssertionDsc& curAssertion, ASSERT_TP& result); #ifdef DEBUG - void optPrintAssertion(AssertionDsc* newAssertion, AssertionIndex assertionIndex = 0); + void optPrintAssertion(const AssertionDsc& newAssertion, AssertionIndex assertionIndex = 0); void optPrintAssertionIndex(AssertionIndex index); void optPrintAssertionIndices(ASSERT_TP assertions); - void optDebugCheckAssertion(AssertionDsc* assertion); + void optDebugCheckAssertion(const AssertionDsc& assertion) const; void optDebugCheckAssertions(AssertionIndex AssertionIndex); #endif diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 1fc21ea7913e93..b51bd9ad7c99d6 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -12667,7 +12667,7 @@ void Compiler::fgKillDependentAssertionsSingle(unsigned lclNum DEBUGARG(GenTree* printf("\nThe store "); printTreeID(tree); printf(" using V%02u removes: ", curAssertion->op1.lclNum); - optPrintAssertion(curAssertion, index); + optPrintAssertion(*curAssertion, index); } } @@ -12760,7 +12760,7 @@ void Compiler::fgAssertionGen(GenTree* tree) printf("GenTreeNode creates %sassertion:\n", condition); gtDispTree(tree, nullptr, nullptr, true); printf("In " FMT_BB " New Local ", compCurBB->bbNum); - optPrintAssertion(optGetAssertion(apIndex), apIndex); + optPrintAssertion(*optGetAssertion(apIndex), apIndex); } else { @@ -12791,11 +12791,9 @@ void Compiler::fgAssertionGen(GenTree* tree) ssize_t iconVal = assertion->op2.u1.iconVal; if ((iconVal == 0) || (iconVal == 1)) { - AssertionDsc extraAssertion = - AssertionDsc::CreateSubrange(this, assertion->op1.lclNum, - IntegralRange(SymbolicIntegerValue::Zero, - SymbolicIntegerValue::One)); - AssertionIndex extraIndex = optAddAssertion(&extraAssertion); + auto range = IntegralRange(SymbolicIntegerValue::Zero, SymbolicIntegerValue::One); + AssertionDsc extraAssertion = AssertionDsc::CreateSubrange(this, assertion->op1.lclNum, range); + AssertionIndex extraIndex = optAddAssertion(extraAssertion); if (extraIndex != NO_ASSERTION_INDEX) { unsigned const bvIndex = extraIndex - 1; diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index 8ec2b30ccd4fa0..e76f6123062e1d 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -1107,7 +1107,7 @@ void RangeCheck::MergeEdgeAssertions(Compiler* comp, #ifdef DEBUG if (comp->verbose) { - comp->optPrintAssertion(curAssertion, assertionIndex); + comp->optPrintAssertion(*curAssertion, assertionIndex); } #endif From e4e68ef8b7512445f4c19310aeb3dd7e73d7e5f2 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sat, 31 Jan 2026 22:31:19 +0100 Subject: [PATCH 10/17] more const references --- src/coreclr/jit/assertionprop.cpp | 237 +++++++++++++++--------------- src/coreclr/jit/compiler.h | 11 +- src/coreclr/jit/compiler.hpp | 82 ++--------- src/coreclr/jit/morph.cpp | 24 +-- src/coreclr/jit/rangecheck.cpp | 46 +++--- 5 files changed, 170 insertions(+), 230 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index e41c297e16e27f..11abb224be402c 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1064,14 +1064,14 @@ void Compiler::optDumpAssertionIndices(ASSERT_TP assertions, const char* footer * is NO_ASSERTION_INDEX and "optAssertionCount" is the last valid index. * */ -Compiler::AssertionDsc* Compiler::optGetAssertion(AssertionIndex assertIndex) const +const Compiler::AssertionDsc& Compiler::optGetAssertion(AssertionIndex assertIndex) const { assert(NO_ASSERTION_INDEX == 0); assert(assertIndex != NO_ASSERTION_INDEX); assert(assertIndex <= optAssertionCount); - AssertionDsc* assertion = &optAssertionTabPrivate[assertIndex - 1]; + const AssertionDsc& assertion = optAssertionTabPrivate[assertIndex - 1]; #ifdef DEBUG - optDebugCheckAssertion(*assertion); + optDebugCheckAssertion(assertion); #endif return assertion; @@ -1564,9 +1564,9 @@ AssertionIndex Compiler::optAddAssertion(const AssertionDsc& newAssertion) while (iter.NextElem(&bvIndex)) { AssertionIndex const index = GetAssertionIndex(bvIndex); - AssertionDsc* const curAssertion = optGetAssertion(index); + const AssertionDsc& curAssertion = optGetAssertion(index); - if (curAssertion->Equals(newAssertion, /* vnBased */ false)) + if (curAssertion.Equals(newAssertion, /* vnBased */ false)) { return index; } @@ -1579,8 +1579,8 @@ AssertionIndex Compiler::optAddAssertion(const AssertionDsc& newAssertion) // Check if exists already, so we can skip adding new one. Search backwards. for (AssertionIndex index = optAssertionCount; index >= 1; index--) { - AssertionDsc* curAssertion = optGetAssertion(index); - if (curAssertion->Equals(newAssertion, /* vnBased */ true)) + const AssertionDsc& curAssertion = optGetAssertion(index); + if (curAssertion.Equals(newAssertion, /* vnBased */ true)) { return index; } @@ -1706,8 +1706,8 @@ void Compiler::optDebugCheckAssertions(AssertionIndex index) AssertionIndex end = (index == NO_ASSERTION_INDEX) ? optAssertionCount : index; for (AssertionIndex ind = start; ind <= end; ++ind) { - AssertionDsc* assertion = optGetAssertion(ind); - optDebugCheckAssertion(*assertion); + const AssertionDsc& assertion = optGetAssertion(ind); + optDebugCheckAssertion(assertion); } } #endif @@ -1732,7 +1732,7 @@ void Compiler::optCreateComplementaryAssertion(AssertionIndex assertionIndex, Ge return; } - AssertionDsc& candidateAssertion = *optGetAssertion(assertionIndex); + const AssertionDsc& candidateAssertion = optGetAssertion(assertionIndex); if ((candidateAssertion.op1.kind == O1K_BOUND_OPER_BND) || (candidateAssertion.op1.kind == O1K_BOUND_LOOP_BND) || (candidateAssertion.op1.kind == O1K_CONSTANT_LOOP_BND) || (candidateAssertion.op1.kind == O1K_CONSTANT_LOOP_BND_UN)) @@ -2220,10 +2220,10 @@ AssertionIndex Compiler::optFindComplementary(AssertionIndex assertIndex) { return NO_ASSERTION_INDEX; } - AssertionDsc* inputAssertion = optGetAssertion(assertIndex); + const AssertionDsc& inputAssertion = optGetAssertion(assertIndex); // Must be an equal or not equal assertion. - if (inputAssertion->assertionKind != OAK_EQUAL && inputAssertion->assertionKind != OAK_NOT_EQUAL) + if (inputAssertion.assertionKind != OAK_EQUAL && inputAssertion.assertionKind != OAK_NOT_EQUAL) { return NO_ASSERTION_INDEX; } @@ -2237,8 +2237,8 @@ AssertionIndex Compiler::optFindComplementary(AssertionIndex assertIndex) for (AssertionIndex index = 1; index <= optAssertionCount; ++index) { // Make sure assertion kinds are complementary and op1, op2 kinds match. - AssertionDsc* curAssertion = optGetAssertion(index); - if (curAssertion->Complementary(*inputAssertion, !optLocalAssertionProp)) + const AssertionDsc& curAssertion = optGetAssertion(index); + if (curAssertion.Complementary(inputAssertion, !optLocalAssertionProp)) { optMapComplementary(assertIndex, index); return index; @@ -2275,19 +2275,19 @@ AssertionIndex Compiler::optAssertionIsSubrange(GenTree* tree, IntegralRange ran while (iter.NextElem(&bvIndex)) { AssertionIndex const index = GetAssertionIndex(bvIndex); - AssertionDsc* const curAssertion = optGetAssertion(index); - if (curAssertion->CanPropSubRange()) + const AssertionDsc& curAssertion = optGetAssertion(index); + if (curAssertion.CanPropSubRange()) { // For local assertion prop use comparison on locals, and use comparison on vns for global prop. bool isEqual = optLocalAssertionProp - ? (curAssertion->op1.lclNum == tree->AsLclVarCommon()->GetLclNum()) - : (curAssertion->op1.vn == vnStore->VNConservativeNormalValue(tree->gtVNPair)); + ? (curAssertion.op1.lclNum == tree->AsLclVarCommon()->GetLclNum()) + : (curAssertion.op1.vn == vnStore->VNConservativeNormalValue(tree->gtVNPair)); if (!isEqual) { continue; } - if (range.Contains(curAssertion->op2.u2)) + if (range.Contains(curAssertion.op2.u2)) { return index; } @@ -2313,9 +2313,9 @@ AssertionIndex Compiler::optAssertionIsSubtype(GenTree* tree, GenTree* methodTab while (iter.NextElem(&bvIndex)) { AssertionIndex const index = GetAssertionIndex(bvIndex); - AssertionDsc* curAssertion = optGetAssertion(index); - if ((curAssertion->assertionKind != OAK_EQUAL) || - ((curAssertion->op1.kind != O1K_SUBTYPE) && (curAssertion->op1.kind != O1K_EXACT_TYPE))) + const AssertionDsc& curAssertion = optGetAssertion(index); + if ((curAssertion.assertionKind != OAK_EQUAL) || + ((curAssertion.op1.kind != O1K_SUBTYPE) && (curAssertion.op1.kind != O1K_EXACT_TYPE))) { // TODO-CQ: We might benefit from OAK_NOT_EQUAL assertion as well, e.g.: // if (obj is not MyClass) // obj is known to be never of MyClass class @@ -2326,8 +2326,8 @@ AssertionIndex Compiler::optAssertionIsSubtype(GenTree* tree, GenTree* methodTab continue; } - if ((curAssertion->op1.vn != vnStore->VNConservativeNormalValue(tree->gtVNPair) || - (curAssertion->op2.kind != O2K_CONST_INT))) + if ((curAssertion.op1.vn != vnStore->VNConservativeNormalValue(tree->gtVNPair) || + (curAssertion.op2.kind != O2K_CONST_INT))) { continue; } @@ -2339,7 +2339,7 @@ AssertionIndex Compiler::optAssertionIsSubtype(GenTree* tree, GenTree* methodTab continue; } - if (curAssertion->op2.u1.iconVal == methodTableVal) + if (curAssertion.op2.u1.iconVal == methodTableVal) { // TODO-CQ: if they don't match, we might still be able to prove that the result is foldable via // compareTypesForCast. @@ -3237,9 +3237,9 @@ bool Compiler::optZeroObjAssertionProp(GenTree* tree, ASSERT_VALARG_TP assertion return false; } - AssertionDsc* assertion = optGetAssertion(assertionIndex); + const AssertionDsc& assertion = optGetAssertion(assertionIndex); JITDUMP("\nAssertion prop in " FMT_BB ":\n", compCurBB->bbNum); - JITDUMPEXEC(optPrintAssertion(*assertion, assertionIndex)); + JITDUMPEXEC(optPrintAssertion(assertion, assertionIndex)); DISPNODE(tree); tree->BashToZeroConst(TYP_INT); @@ -3460,14 +3460,14 @@ GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeL break; } // See if the variable is equal to a constant or another variable. - AssertionDsc* curAssertion = optGetAssertion(assertionIndex); - if (!curAssertion->CanPropLclVar()) + const AssertionDsc& curAssertion = optGetAssertion(assertionIndex); + if (!curAssertion.CanPropLclVar()) { continue; } // Copy prop. - if (curAssertion->op2.kind == O2K_LCLVAR_COPY) + if (curAssertion.op2.kind == O2K_LCLVAR_COPY) { // Cannot do copy prop during global assertion prop because of no knowledge // of kill sets. We will still make a == b copy assertions during the global phase to allow @@ -3476,7 +3476,7 @@ GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeL if (optLocalAssertionProp) { // Perform copy assertion prop. - GenTree* newTree = optCopyAssertionProp(*curAssertion, tree, stmt DEBUGARG(assertionIndex)); + GenTree* newTree = optCopyAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex)); if (newTree != nullptr) { return newTree; @@ -3502,17 +3502,17 @@ GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeL if (optLocalAssertionProp) { // Check lclNum in Local Assertion Prop - if (curAssertion->op1.lclNum == lclNum) + if (curAssertion.op1.lclNum == lclNum) { - return optConstantAssertionProp(*curAssertion, tree, stmt DEBUGARG(assertionIndex)); + return optConstantAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex)); } } else { // Check VN in Global Assertion Prop - if (curAssertion->op1.vn == vnStore->VNConservativeNormalValue(tree->gtVNPair)) + if (curAssertion.op1.vn == vnStore->VNConservativeNormalValue(tree->gtVNPair)) { - return optConstantAssertionProp(*curAssertion, tree, stmt DEBUGARG(assertionIndex)); + return optConstantAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex)); } } } @@ -3567,10 +3567,10 @@ GenTree* Compiler::optAssertionProp_LclFld(ASSERT_VALARG_TP assertions, GenTreeL // See if the variable is equal to another variable. // - AssertionDsc* const curAssertion = optGetAssertion(assertionIndex); - if (curAssertion->CanPropLclVar() && (curAssertion->op2.kind == O2K_LCLVAR_COPY)) + const AssertionDsc& curAssertion = optGetAssertion(assertionIndex); + if (curAssertion.CanPropLclVar() && (curAssertion.op2.kind == O2K_LCLVAR_COPY)) { - GenTree* const newTree = optCopyAssertionProp(*curAssertion, tree, stmt DEBUGARG(assertionIndex)); + GenTree* const newTree = optCopyAssertionProp(curAssertion, tree, stmt DEBUGARG(assertionIndex)); if (newTree != nullptr) { return newTree; @@ -3632,8 +3632,8 @@ GenTree* Compiler::optAssertionProp_LocalStore(ASSERT_VALARG_TP assertions, GenT assertions); if (dstIndex != NO_ASSERTION_INDEX) { - AssertionDsc* const dstAssertion = optGetAssertion(dstIndex); - if ((dstAssertion->assertionKind == OAK_EQUAL) && (dstAssertion->op2.u1.iconVal == 0)) + const AssertionDsc& dstAssertion = optGetAssertion(dstIndex); + if ((dstAssertion.assertionKind == OAK_EQUAL) && (dstAssertion.op2.u1.iconVal == 0)) { // Destination is zero. Is value a literal zero? If so we don't need the store. // @@ -3647,7 +3647,7 @@ GenTree* Compiler::optAssertionProp_LocalStore(ASSERT_VALARG_TP assertions, GenT { JITDUMP("[%06u] is assigning a constant zero to a struct field or gc local that is already zero\n", dspTreeID(store)); - JITDUMPEXEC(optPrintAssertion(*dstAssertion)); + JITDUMPEXEC(optPrintAssertion(dstAssertion)); store->gtBashToNOP(); return optAssertionProp_Update(store, store, stmt); @@ -3737,7 +3737,7 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions, unsigned index = 0; while (iter.NextElem(&index)) { - AssertionDsc* curAssertion = optGetAssertion(GetAssertionIndex(index)); + const AssertionDsc& curAssertion = optGetAssertion(GetAssertionIndex(index)); // if treeVN has a bound-check assertion where it's an index, then // it means it's not negative, example: @@ -3745,7 +3745,7 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions, // array[idx] = 42; // creates 'BoundsCheckNoThrow' assertion // return idx % 8; // idx is known to be never negative here, hence, MOD->UMOD // - if (curAssertion->IsBoundsCheckNoThrow() && (curAssertion->op1.bnd.vnIdx == treeVN)) + if (curAssertion.IsBoundsCheckNoThrow() && (curAssertion.op1.bnd.vnIdx == treeVN)) { *isKnownNonNegative = true; continue; @@ -3756,7 +3756,7 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions, // array[idx] = 42; // array.Length is known to be non-negative and non-zero here // - if (curAssertion->IsBoundsCheckNoThrow() && (curAssertion->op1.bnd.vnLen == treeVN)) + if (curAssertion.IsBoundsCheckNoThrow() && (curAssertion.op1.bnd.vnLen == treeVN)) { *isKnownNonNegative = true; *isKnownNonZero = true; @@ -3764,32 +3764,32 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions, } // First, analyze possible X ==/!= CNS assertions. - if (curAssertion->IsConstantInt32Assertion() && (curAssertion->op1.vn == treeVN)) + if (curAssertion.IsConstantInt32Assertion() && (curAssertion.op1.vn == treeVN)) { - if ((curAssertion->assertionKind == OAK_NOT_EQUAL) && (curAssertion->op2.u1.iconVal == 0)) + if ((curAssertion.assertionKind == OAK_NOT_EQUAL) && (curAssertion.op2.u1.iconVal == 0)) { // X != 0 --> definitely non-zero // We can't say anything about X's non-negativity *isKnownNonZero = true; } - else if (curAssertion->assertionKind != OAK_NOT_EQUAL) + else if (curAssertion.assertionKind != OAK_NOT_EQUAL) { // X == CNS --> definitely non-negative if CNS >= 0 // and definitely non-zero if CNS != 0 - *isKnownNonNegative = curAssertion->op2.u1.iconVal >= 0; - *isKnownNonZero = curAssertion->op2.u1.iconVal != 0; + *isKnownNonNegative = curAssertion.op2.u1.iconVal >= 0; + *isKnownNonZero = curAssertion.op2.u1.iconVal != 0; } } // OAK_[NOT]_EQUAL assertion with op1 being O1K_CONSTANT_LOOP_BND // representing "(X relop CNS) ==/!= 0" assertion. - if (!curAssertion->IsConstantBound() && !curAssertion->IsConstantBoundUnsigned()) + if (!curAssertion.IsConstantBound() && !curAssertion.IsConstantBoundUnsigned()) { continue; } ValueNumStore::ConstantBoundInfo info; - vnStore->GetConstantBoundInfo(curAssertion->op1.vn, &info); + vnStore->GetConstantBoundInfo(curAssertion.op1.vn, &info); if (info.cmpOpVN != treeVN) { @@ -3799,7 +3799,7 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions, // Root assertion has to be either: // (X relop CNS) == 0 // (X relop CNS) != 0 - if ((curAssertion->op2.kind != O2K_CONST_INT) || (curAssertion->op2.u1.iconVal != 0)) + if ((curAssertion.op2.kind != O2K_CONST_INT) || (curAssertion.op2.u1.iconVal != 0)) { continue; } @@ -3807,7 +3807,7 @@ void Compiler::optAssertionProp_RangeProperties(ASSERT_VALARG_TP assertions, genTreeOps cmpOper = static_cast(info.cmpOper); // Normalize "(X relop CNS) == false" to "(X reversed_relop CNS) == true" - if (curAssertion->assertionKind == OAK_EQUAL) + if (curAssertion.assertionKind == OAK_EQUAL) { cmpOper = GenTree::ReverseRelop(cmpOper); } @@ -3955,18 +3955,18 @@ AssertionIndex Compiler::optLocalAssertionIsEqualOrNotEqual( while (iter.NextElem(&bvIndex)) { AssertionIndex const index = GetAssertionIndex(bvIndex); - AssertionDsc* curAssertion = optGetAssertion(index); + const AssertionDsc& curAssertion = optGetAssertion(index); - if ((curAssertion->assertionKind != OAK_EQUAL) && (curAssertion->assertionKind != OAK_NOT_EQUAL)) + if ((curAssertion.assertionKind != OAK_EQUAL) && (curAssertion.assertionKind != OAK_NOT_EQUAL)) { continue; } - if ((curAssertion->op1.kind == op1Kind) && (curAssertion->op1.lclNum == lclNum) && - (curAssertion->op2.kind == op2Kind)) + if ((curAssertion.op1.kind == op1Kind) && (curAssertion.op1.lclNum == lclNum) && + (curAssertion.op2.kind == op2Kind)) { - bool constantIsEqual = (curAssertion->op2.u1.iconVal == cnsVal); - bool assertionIsEqual = (curAssertion->assertionKind == OAK_EQUAL); + bool constantIsEqual = (curAssertion.op2.u1.iconVal == cnsVal); + bool assertionIsEqual = (curAssertion.assertionKind == OAK_EQUAL); if (constantIsEqual || assertionIsEqual) { @@ -4010,14 +4010,14 @@ AssertionIndex Compiler::optGlobalAssertionIsEqualOrNotEqual(ASSERT_VALARG_TP as { break; } - AssertionDsc* curAssertion = optGetAssertion(assertionIndex); - if (!curAssertion->CanPropEqualOrNotEqual()) + const AssertionDsc& curAssertion = optGetAssertion(assertionIndex); + if (!curAssertion.CanPropEqualOrNotEqual()) { continue; } - if ((curAssertion->op1.vn == vnStore->VNConservativeNormalValue(op1->gtVNPair)) && - (curAssertion->op2.vn == vnStore->VNConservativeNormalValue(op2->gtVNPair))) + if ((curAssertion.op1.vn == vnStore->VNConservativeNormalValue(op1->gtVNPair)) && + (curAssertion.op2.vn == vnStore->VNConservativeNormalValue(op2->gtVNPair))) { return assertionIndex; } @@ -4028,12 +4028,12 @@ AssertionIndex Compiler::optGlobalAssertionIsEqualOrNotEqual(ASSERT_VALARG_TP as // op2: 'MyType' class handle // Assertion: 'myObj's type is exactly MyType // - if ((curAssertion->assertionKind == OAK_EQUAL) && (curAssertion->op1.kind == O1K_EXACT_TYPE) && - (curAssertion->op2.vn == vnStore->VNConservativeNormalValue(op2->gtVNPair)) && op1->TypeIs(TYP_I_IMPL)) + if ((curAssertion.assertionKind == OAK_EQUAL) && (curAssertion.op1.kind == O1K_EXACT_TYPE) && + (curAssertion.op2.vn == vnStore->VNConservativeNormalValue(op2->gtVNPair)) && op1->TypeIs(TYP_I_IMPL)) { VNFuncApp funcApp; if (vnStore->GetVNFunc(vnStore->VNConservativeNormalValue(op1->gtVNPair), &funcApp) && - (funcApp.m_func == VNF_InvariantNonNullLoad) && (curAssertion->op1.vn == funcApp.m_args[0])) + (funcApp.m_func == VNF_InvariantNonNullLoad) && (curAssertion.op1.vn == funcApp.m_args[0])) { return assertionIndex; } @@ -4063,14 +4063,14 @@ AssertionIndex Compiler::optGlobalAssertionIsEqualOrNotEqualZero(ASSERT_VALARG_T { break; } - AssertionDsc* curAssertion = optGetAssertion(assertionIndex); - if (!curAssertion->CanPropEqualOrNotEqual()) + const AssertionDsc& curAssertion = optGetAssertion(assertionIndex); + if (!curAssertion.CanPropEqualOrNotEqual()) { continue; } - if ((curAssertion->op1.vn == vnStore->VNConservativeNormalValue(op1->gtVNPair)) && - (curAssertion->op2.vn == vnStore->VNZeroForType(op1->TypeGet()))) + if ((curAssertion.op1.vn == vnStore->VNConservativeNormalValue(op1->gtVNPair)) && + (curAssertion.op2.vn == vnStore->VNZeroForType(op1->TypeGet()))) { return assertionIndex; } @@ -4178,7 +4178,7 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, if (index != NO_ASSERTION_INDEX) { // We know that this relop is either 0 or != 0 (1) - AssertionDsc* curAssertion = optGetAssertion(index); + const AssertionDsc& curAssertion = optGetAssertion(index); #ifdef DEBUG if (verbose) @@ -4186,11 +4186,11 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, printf("\nVN relop based constant assertion prop in " FMT_BB ":\n", compCurBB->bbNum); printf("Assertion index=#%02u: ", index); printTreeID(tree); - printf(" %s 0\n", (curAssertion->assertionKind == OAK_EQUAL) ? "==" : "!="); + printf(" %s 0\n", (curAssertion.assertionKind == OAK_EQUAL) ? "==" : "!="); } #endif - newTree = curAssertion->assertionKind == OAK_EQUAL ? gtNewIconNode(0) : gtNewIconNode(1); + newTree = curAssertion.assertionKind == OAK_EQUAL ? gtNewIconNode(0) : gtNewIconNode(1); newTree = gtWrapWithSideEffects(newTree, tree, GTF_ALL_EFFECT); DISPTREE(newTree); return optAssertionProp_Update(newTree, tree, stmt); @@ -4256,8 +4256,8 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, return nullptr; } - AssertionDsc* curAssertion = optGetAssertion(index); - bool assertionKindIsEqual = (curAssertion->assertionKind == OAK_EQUAL); + const AssertionDsc& curAssertion = optGetAssertion(index); + bool assertionKindIsEqual = (curAssertion.assertionKind == OAK_EQUAL); // Allow or not to reverse condition for OAK_NOT_EQUAL assertions. bool allowReverse = true; @@ -4391,7 +4391,7 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, printf("\nVN relop based copy assertion prop in " FMT_BB ":\n", compCurBB->bbNum); printf("Assertion index=#%02u: V%02d.%02d %s V%02d.%02d\n", index, op1->AsLclVar()->GetLclNum(), op1->AsLclVar()->GetSsaNum(), - (curAssertion->assertionKind == OAK_EQUAL) ? "==" : "!=", op2->AsLclVar()->GetLclNum(), + (curAssertion.assertionKind == OAK_EQUAL) ? "==" : "!=", op2->AsLclVar()->GetLclNum(), op2->AsLclVar()->GetSsaNum()); gtDispTree(tree, nullptr, nullptr, true); } @@ -4422,7 +4422,7 @@ GenTree* Compiler::optAssertionPropGlobal_RelOp(ASSERT_VALARG_TP assertions, } // Finally reverse the condition, if we have a not equal assertion. - if (allowReverse && curAssertion->assertionKind == OAK_NOT_EQUAL) + if (allowReverse && curAssertion.assertionKind == OAK_NOT_EQUAL) { gtReverseCond(tree); } @@ -4492,20 +4492,20 @@ GenTree* Compiler::optAssertionPropLocal_RelOp(ASSERT_VALARG_TP assertions, GenT return nullptr; } - AssertionDsc* curAssertion = optGetAssertion(index); + const AssertionDsc& curAssertion = optGetAssertion(index); - bool assertionKindIsEqual = (curAssertion->assertionKind == OAK_EQUAL); + bool assertionKindIsEqual = (curAssertion.assertionKind == OAK_EQUAL); bool constantIsEqual = false; if (genTypeSize(cmpType) == TARGET_POINTER_SIZE) { - constantIsEqual = (curAssertion->op2.u1.iconVal == cnsVal); + constantIsEqual = (curAssertion.op2.u1.iconVal == cnsVal); } #ifdef TARGET_64BIT else if (genTypeSize(cmpType) == sizeof(INT32)) { // Compare the low 32-bits only - constantIsEqual = (((INT32)curAssertion->op2.u1.iconVal) == ((INT32)cnsVal)); + constantIsEqual = (((INT32)curAssertion.op2.u1.iconVal) == ((INT32)cnsVal)); } #endif else @@ -4748,9 +4748,9 @@ bool Compiler::optAssertionIsNonNull(GenTree* op, ASSERT_VALARG_TP assertions) unsigned index = 0; while (iter.NextElem(&index)) { - AssertionIndex assertionIndex = GetAssertionIndex(index); - AssertionDsc* curAssertion = optGetAssertion(assertionIndex); - if (curAssertion->CanPropNonNull() && ((curAssertion->op1.vn == vn) || (curAssertion->op1.vn == vnBase))) + AssertionIndex assertionIndex = GetAssertionIndex(index); + const AssertionDsc& curAssertion = optGetAssertion(assertionIndex); + if (curAssertion.CanPropNonNull() && ((curAssertion.op1.vn == vn) || (curAssertion.op1.vn == vnBase))) { return true; } @@ -4769,13 +4769,13 @@ bool Compiler::optAssertionIsNonNull(GenTree* op, ASSERT_VALARG_TP assertions) unsigned index = 0; while (iter.NextElem(&index)) { - AssertionIndex assertionIndex = GetAssertionIndex(index); - AssertionDsc* curAssertion = optGetAssertion(assertionIndex); + AssertionIndex assertionIndex = GetAssertionIndex(index); + const AssertionDsc& curAssertion = optGetAssertion(assertionIndex); - if ((curAssertion->assertionKind == OAK_NOT_EQUAL) && // kind - (curAssertion->op1.kind == O1K_LCLVAR) && // op1 - (curAssertion->op2.kind == O2K_CONST_INT) && // op2 - (curAssertion->op1.lclNum == lclNum) && (curAssertion->op2.u1.iconVal == 0)) + if ((curAssertion.assertionKind == OAK_NOT_EQUAL) && // kind + (curAssertion.op1.kind == O1K_LCLVAR) && // op1 + (curAssertion.op2.kind == O2K_CONST_INT) && // op2 + (curAssertion.op1.lclNum == lclNum) && (curAssertion.op2.u1.iconVal == 0)) { return true; } @@ -4808,8 +4808,8 @@ bool Compiler::optAssertionVNIsNonNull(ValueNum vn, ASSERT_VALARG_TP assertions) unsigned index = 0; while (iter.NextElem(&index)) { - AssertionDsc* curAssertion = optGetAssertion(GetAssertionIndex(index)); - if (curAssertion->CanPropNonNull() && curAssertion->op1.vn == vn) + const AssertionDsc& curAssertion = optGetAssertion(GetAssertionIndex(index)); + if (curAssertion.CanPropNonNull() && curAssertion.op1.vn == vn) { return true; } @@ -5163,18 +5163,18 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree break; } // If it is not a nothrow assertion, skip. - AssertionDsc* curAssertion = optGetAssertion(assertionIndex); - if (!curAssertion->IsBoundsCheckNoThrow()) + const AssertionDsc& curAssertion = optGetAssertion(assertionIndex); + if (!curAssertion.IsBoundsCheckNoThrow()) { continue; } // Do we have a previous range check involving the same 'vnLen' upper bound? - if (curAssertion->op1.bnd.vnLen == vnStore->VNConservativeNormalValue(arrBndsChk->GetArrayLength()->gtVNPair)) + if (curAssertion.op1.bnd.vnLen == vnStore->VNConservativeNormalValue(arrBndsChk->GetArrayLength()->gtVNPair)) { // Do we have the exact same lower bound 'vnIdx'? // a[i] followed by a[i] - if (curAssertion->op1.bnd.vnIdx == vnCurIdx) + if (curAssertion.op1.bnd.vnIdx == vnCurIdx) { return dropBoundsCheck(INDEBUG("a[i] followed by a[i]")); } @@ -5214,7 +5214,7 @@ GenTree* Compiler::optAssertionProp_BndsChk(ASSERT_VALARG_TP assertions, GenTree int index1; int index2; - if (tryGetMaxOrMinConst(curAssertion->op1.bnd.vnIdx, /*min*/ true, &index1) && + if (tryGetMaxOrMinConst(curAssertion.op1.bnd.vnIdx, /*min*/ true, &index1) && tryGetMaxOrMinConst(vnCurIdx, /*max*/ false, &index2)) { // It can always be considered as redundant with any previous higher constant value @@ -5411,11 +5411,11 @@ void Compiler::optImpliedAssertions(AssertionIndex assertionIndex, ASSERT_TP& ac // Is curAssertion a constant store of a 32-bit integer? // (i.e GT_LVL_VAR X == GT_CNS_INT) - AssertionDsc* curAssertion = optGetAssertion(assertionIndex); - if ((curAssertion->assertionKind == OAK_EQUAL) && (curAssertion->op1.kind == O1K_LCLVAR) && - (curAssertion->op2.kind == O2K_CONST_INT)) + const AssertionDsc& curAssertion = optGetAssertion(assertionIndex); + if ((curAssertion.assertionKind == OAK_EQUAL) && (curAssertion.op1.kind == O1K_LCLVAR) && + (curAssertion.op2.kind == O2K_CONST_INT)) { - optImpliedByConstAssertion(*curAssertion, activeAssertions); + optImpliedByConstAssertion(curAssertion, activeAssertions); } } @@ -5577,9 +5577,9 @@ void Compiler::optImpliedByTypeOfAssertions(ASSERT_TP& activeAssertions) break; } // chkAssertion must be Type/Subtype is equal assertion - AssertionDsc* chkAssertion = optGetAssertion(chkAssertionIndex); - if ((chkAssertion->op1.kind != O1K_SUBTYPE && chkAssertion->op1.kind != O1K_EXACT_TYPE) || - (chkAssertion->assertionKind != OAK_EQUAL)) + const AssertionDsc& chkAssertion = optGetAssertion(chkAssertionIndex); + if ((chkAssertion.op1.kind != O1K_SUBTYPE && chkAssertion.op1.kind != O1K_EXACT_TYPE) || + (chkAssertion.assertionKind != OAK_EQUAL)) { continue; } @@ -5587,7 +5587,7 @@ void Compiler::optImpliedByTypeOfAssertions(ASSERT_TP& activeAssertions) // Search the assertion table for a non-null assertion on op1 that matches chkAssertion for (AssertionIndex impIndex = 1; impIndex <= optAssertionCount; impIndex++) { - AssertionDsc* impAssertion = optGetAssertion(impIndex); + const AssertionDsc& impAssertion = optGetAssertion(impIndex); // The impAssertion must be different from the chkAssertion if (impIndex == chkAssertionIndex) @@ -5596,8 +5596,8 @@ void Compiler::optImpliedByTypeOfAssertions(ASSERT_TP& activeAssertions) } // impAssertion must be a Non Null assertion on op1.vn - if ((impAssertion->assertionKind != OAK_NOT_EQUAL) || !impAssertion->CanPropNonNull() || - (impAssertion->op1.vn != chkAssertion->op1.vn)) + if ((impAssertion.assertionKind != OAK_NOT_EQUAL) || !impAssertion.CanPropNonNull() || + (impAssertion.op1.vn != chkAssertion.op1.vn)) { continue; } @@ -5606,8 +5606,7 @@ void Compiler::optImpliedByTypeOfAssertions(ASSERT_TP& activeAssertions) if (BitVecOps::TryAddElemD(apTraits, activeAssertions, impIndex - 1)) { JITDUMP("\nCompiler::optImpliedByTypeOfAssertions: %s Assertion #%02d, implies assertion #%02d", - (chkAssertion->op1.kind == O1K_SUBTYPE) ? "Subtype" : "Exact-type", chkAssertionIndex, - impIndex); + (chkAssertion.op1.kind == O1K_SUBTYPE) ? "Subtype" : "Exact-type", chkAssertionIndex, impIndex); } // There is at most one non-null assertion that is implied by the current chkIndex assertion @@ -5691,30 +5690,30 @@ void Compiler::optImpliedByConstAssertion(const AssertionDsc& constAssertion, AS break; } // The impAssertion must be different from the const assertion. - AssertionDsc* impAssertion = optGetAssertion(chkAssertionIndex); - if (impAssertion->Equals(constAssertion, !optLocalAssertionProp)) + const AssertionDsc& impAssertion = optGetAssertion(chkAssertionIndex); + if (impAssertion.Equals(constAssertion, !optLocalAssertionProp)) { continue; } // The impAssertion must be an assertion about the same local var. - if (impAssertion->op1.vn != constAssertion.op1.vn) + if (impAssertion.op1.vn != constAssertion.op1.vn) { continue; } bool usable = false; - switch (impAssertion->op2.kind) + switch (impAssertion.op2.kind) { case O2K_SUBRANGE: // Is the const assertion's constant, within implied assertion's bounds? - usable = impAssertion->op2.u2.Contains(iconVal); + usable = impAssertion.op2.u2.Contains(iconVal); break; case O2K_CONST_INT: // Is the const assertion's constant equal/not equal to the implied assertion? - usable = ((impAssertion->assertionKind == OAK_EQUAL) && (impAssertion->op2.u1.iconVal == iconVal)) || - ((impAssertion->assertionKind == OAK_NOT_EQUAL) && (impAssertion->op2.u1.iconVal != iconVal)); + usable = ((impAssertion.assertionKind == OAK_EQUAL) && (impAssertion.op2.u1.iconVal == iconVal)) || + ((impAssertion.assertionKind == OAK_NOT_EQUAL) && (impAssertion.op2.u1.iconVal != iconVal)); break; default: @@ -5728,9 +5727,9 @@ void Compiler::optImpliedByConstAssertion(const AssertionDsc& constAssertion, AS #ifdef DEBUG if (verbose) { - AssertionDsc* firstAssertion = optGetAssertion(1); - printf("Compiler::optImpliedByConstAssertion: const assertion #%02d implies assertion #%02d\n", - (&constAssertion - firstAssertion) + 1, (impAssertion - firstAssertion) + 1); + printf("Compiler::optImpliedByConstAssertion "); + optPrintAssertion(constAssertion); + printf(" implies assertion #%02d\n", chkAssertionIndex); } #endif } diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 3701b5dde9edbf..13c09803ee27e6 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8258,12 +8258,11 @@ class Compiler ValueNumToAssertsMap* optValueNumToAsserts; // Assertion prop helpers. - ASSERT_TP& GetAssertionDep(unsigned lclNum); - AssertionDsc* optGetAssertion(AssertionIndex assertIndex) const; - void optAssertionInit(bool isLocalProp); - void optAssertionTraitsInit(AssertionIndex assertionCount); - void optAssertionReset(AssertionIndex limit); - void optAssertionRemove(AssertionIndex index); + ASSERT_TP& GetAssertionDep(unsigned lclNum); + const AssertionDsc& optGetAssertion(AssertionIndex assertIndex) const; + void optAssertionInit(bool isLocalProp); + void optAssertionTraitsInit(AssertionIndex assertionCount); + void optAssertionReset(AssertionIndex limit); // Assertion prop data flow functions. PhaseStatus optAssertionPropMain(); diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index ed1a7a1cb4fe05..376a441f6e4437 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -3834,106 +3834,48 @@ inline void Compiler::optAssertionReset(AssertionIndex limit) while (optAssertionCount > limit) { - AssertionIndex index = optAssertionCount; - AssertionDsc* curAssertion = optGetAssertion(index); + AssertionIndex index = optAssertionCount; + const AssertionDsc& curAssertion = optGetAssertion(index); optAssertionCount--; - unsigned lclNum = curAssertion->op1.lclNum; + unsigned lclNum = curAssertion.op1.lclNum; assert(lclNum < lvaCount); BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum), index - 1); // // Find the Copy assertions // - if ((curAssertion->assertionKind == OAK_EQUAL) && (curAssertion->op1.kind == O1K_LCLVAR) && - (curAssertion->op2.kind == O2K_LCLVAR_COPY)) + if ((curAssertion.assertionKind == OAK_EQUAL) && (curAssertion.op1.kind == O1K_LCLVAR) && + (curAssertion.op2.kind == O2K_LCLVAR_COPY)) { // // op2.lclNum no longer depends upon this assertion // - lclNum = curAssertion->op2.lclNum; + lclNum = curAssertion.op2.lclNum; BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum), index - 1); } } while (optAssertionCount < limit) { - AssertionIndex index = ++optAssertionCount; - AssertionDsc* curAssertion = optGetAssertion(index); - unsigned lclNum = curAssertion->op1.lclNum; + AssertionIndex index = ++optAssertionCount; + const AssertionDsc& curAssertion = optGetAssertion(index); + unsigned lclNum = curAssertion.op1.lclNum; BitVecOps::AddElemD(apTraits, GetAssertionDep(lclNum), index - 1); // // Check for Copy assertions // - if ((curAssertion->assertionKind == OAK_EQUAL) && (curAssertion->op1.kind == O1K_LCLVAR) && - (curAssertion->op2.kind == O2K_LCLVAR_COPY)) + if ((curAssertion.assertionKind == OAK_EQUAL) && (curAssertion.op1.kind == O1K_LCLVAR) && + (curAssertion.op2.kind == O2K_LCLVAR_COPY)) { // // op2.lclNum now depends upon this assertion // - lclNum = curAssertion->op2.lclNum; + lclNum = curAssertion.op2.lclNum; BitVecOps::AddElemD(apTraits, GetAssertionDep(lclNum), index - 1); } } } -/***************************************************************************** - * - * The following removes the i-th entry in the assertions table - * used only during local assertion prop - */ - -inline void Compiler::optAssertionRemove(AssertionIndex index) -{ - assert(index > 0); - assert(index <= optAssertionCount); - assert(optAssertionCount <= optMaxAssertionCount); - - AssertionDsc* curAssertion = optGetAssertion(index); - - // Two cases to consider if (index == optAssertionCount) then the last - // entry in the table is to be removed and that happens automatically when - // optAssertionCount is decremented and we can just clear the optAssertionDep bits - // The other case is when index < optAssertionCount and here we overwrite the - // index-th entry in the table with the data found at the end of the table - // Since we are reordering the rable the optAssertionDep bits need to be recreated - // using optAssertionReset(0) and optAssertionReset(newAssertionCount) will - // correctly update the optAssertionDep bits - // - if (index == optAssertionCount) - { - unsigned lclNum = curAssertion->op1.lclNum; - BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum), index - 1); - - // - // Check for Copy assertions - // - if ((curAssertion->assertionKind == OAK_EQUAL) && (curAssertion->op1.kind == O1K_LCLVAR) && - (curAssertion->op2.kind == O2K_LCLVAR_COPY)) - { - // - // op2.lclNum no longer depends upon this assertion - // - lclNum = curAssertion->op2.lclNum; - BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum), index - 1); - } - - optAssertionCount--; - } - else - { - AssertionDsc* lastAssertion = optGetAssertion(optAssertionCount); - AssertionIndex newAssertionCount = optAssertionCount - 1; - - optAssertionReset(0); // This make optAssertionCount equal 0 - - memcpy(curAssertion, // the entry to be removed - lastAssertion, // last entry in the table - sizeof(AssertionDsc)); - - optAssertionReset(newAssertionCount); - } -} - /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index b51bd9ad7c99d6..f2f6937dd067e3 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -12659,15 +12659,15 @@ void Compiler::fgKillDependentAssertionsSingle(unsigned lclNum DEBUGARG(GenTree* { if (BitVecOps::IsMember(apTraits, killed, index - 1)) { - AssertionDsc* curAssertion = optGetAssertion(index); - noway_assert((curAssertion->op1.lclNum == lclNum) || - ((curAssertion->op2.kind == O2K_LCLVAR_COPY) && (curAssertion->op2.lclNum == lclNum))); + const AssertionDsc& curAssertion = optGetAssertion(index); + noway_assert((curAssertion.op1.lclNum == lclNum) || + ((curAssertion.op2.kind == O2K_LCLVAR_COPY) && (curAssertion.op2.lclNum == lclNum))); if (verbose) { printf("\nThe store "); printTreeID(tree); - printf(" using V%02u removes: ", curAssertion->op1.lclNum); - optPrintAssertion(*curAssertion, index); + printf(" using V%02u removes: ", curAssertion.op1.lclNum); + optPrintAssertion(curAssertion, index); } } @@ -12760,7 +12760,7 @@ void Compiler::fgAssertionGen(GenTree* tree) printf("GenTreeNode creates %sassertion:\n", condition); gtDispTree(tree, nullptr, nullptr, true); printf("In " FMT_BB " New Local ", compCurBB->bbNum); - optPrintAssertion(*optGetAssertion(apIndex), apIndex); + optPrintAssertion(optGetAssertion(apIndex), apIndex); } else { @@ -12780,19 +12780,19 @@ void Compiler::fgAssertionGen(GenTree* tree) // assertion for that local, in case this local is used as a bool. // auto addImpliedAssertions = [=](AssertionIndex index, ASSERT_TP& assertions) { - AssertionDsc* const assertion = optGetAssertion(index); - if ((assertion->assertionKind == OAK_EQUAL) && (assertion->op1.kind == O1K_LCLVAR) && - (assertion->op2.kind == O2K_CONST_INT)) + const AssertionDsc& assertion = optGetAssertion(index); + if ((assertion.assertionKind == OAK_EQUAL) && (assertion.op1.kind == O1K_LCLVAR) && + (assertion.op2.kind == O2K_CONST_INT)) { - LclVarDsc* const lclDsc = lvaGetDesc(assertion->op1.lclNum); + LclVarDsc* const lclDsc = lvaGetDesc(assertion.op1.lclNum); if (varTypeIsIntegral(lclDsc->TypeGet())) { - ssize_t iconVal = assertion->op2.u1.iconVal; + ssize_t iconVal = assertion.op2.u1.iconVal; if ((iconVal == 0) || (iconVal == 1)) { auto range = IntegralRange(SymbolicIntegerValue::Zero, SymbolicIntegerValue::One); - AssertionDsc extraAssertion = AssertionDsc::CreateSubrange(this, assertion->op1.lclNum, range); + AssertionDsc extraAssertion = AssertionDsc::CreateSubrange(this, assertion.op1.lclNum, range); AssertionIndex extraIndex = optAddAssertion(extraAssertion); if (extraIndex != NO_ASSERTION_INDEX) { diff --git a/src/coreclr/jit/rangecheck.cpp b/src/coreclr/jit/rangecheck.cpp index e76f6123062e1d..a70e00c57987d6 100644 --- a/src/coreclr/jit/rangecheck.cpp +++ b/src/coreclr/jit/rangecheck.cpp @@ -903,7 +903,7 @@ void RangeCheck::MergeEdgeAssertions(Compiler* comp, { AssertionIndex assertionIndex = GetAssertionIndex(index); - Compiler::AssertionDsc* curAssertion = comp->optGetAssertion(assertionIndex); + const Compiler::AssertionDsc& curAssertion = comp->optGetAssertion(assertionIndex); Limit limit(Limit::keUndef); genTreeOps cmpOper = GT_NONE; @@ -911,12 +911,12 @@ void RangeCheck::MergeEdgeAssertions(Compiler* comp, bool isUnsigned = false; // Current assertion is of the form (i < len - cns) != 0 - if (canUseCheckedBounds && curAssertion->IsCheckedBoundArithBound()) + if (canUseCheckedBounds && curAssertion.IsCheckedBoundArithBound()) { ValueNumStore::CompareCheckedBoundArithInfo info; // Get i, len, cns and < as "info." - comp->vnStore->GetCompareCheckedBoundArithInfo(curAssertion->op1.vn, &info); + comp->vnStore->GetCompareCheckedBoundArithInfo(curAssertion.op1.vn, &info); // If we don't have the same variable we are comparing against, bail. if (normalLclVN != info.cmpOp) @@ -940,12 +940,12 @@ void RangeCheck::MergeEdgeAssertions(Compiler* comp, cmpOper = (genTreeOps)info.cmpOper; } // Current assertion is of the form (i < len) != 0 - else if (canUseCheckedBounds && curAssertion->IsCheckedBoundBound()) + else if (canUseCheckedBounds && curAssertion.IsCheckedBoundBound()) { ValueNumStore::CompareCheckedBoundArithInfo info; // Get the info as "i", "<" and "len" - comp->vnStore->GetCompareCheckedBound(curAssertion->op1.vn, &info); + comp->vnStore->GetCompareCheckedBound(curAssertion.op1.vn, &info); // If we don't have the same variable we are comparing against, bail. if (normalLclVN == info.cmpOp) @@ -964,12 +964,12 @@ void RangeCheck::MergeEdgeAssertions(Compiler* comp, } } // Current assertion is of the form (i < 100) != 0 - else if (curAssertion->IsConstantBound() || curAssertion->IsConstantBoundUnsigned()) + else if (curAssertion.IsConstantBound() || curAssertion.IsConstantBoundUnsigned()) { ValueNumStore::ConstantBoundInfo info; // Get the info as "i", "<" and "100" - comp->vnStore->GetConstantBoundInfo(curAssertion->op1.vn, &info); + comp->vnStore->GetConstantBoundInfo(curAssertion.op1.vn, &info); // If we don't have the same variable we are comparing against, bail. if (normalLclVN != info.cmpOpVN) @@ -982,37 +982,37 @@ void RangeCheck::MergeEdgeAssertions(Compiler* comp, isUnsigned = info.isUnsigned; } // Current assertion is of the form i == 100 - else if (curAssertion->IsConstantInt32Assertion()) + else if (curAssertion.IsConstantInt32Assertion()) { - if (curAssertion->op1.vn != normalLclVN) + if (curAssertion.op1.vn != normalLclVN) { continue; } // Ignore GC values/NULL caught by IsConstantInt32Assertion assertion (may happen on 32bit) - if (varTypeIsGC(comp->vnStore->TypeOfVN(curAssertion->op2.vn))) + if (varTypeIsGC(comp->vnStore->TypeOfVN(curAssertion.op2.vn))) { continue; } - int cnstLimit = (int)curAssertion->op2.u1.iconVal; - assert(cnstLimit == comp->vnStore->CoercedConstantValue(curAssertion->op2.vn)); + int cnstLimit = (int)curAssertion.op2.u1.iconVal; + assert(cnstLimit == comp->vnStore->CoercedConstantValue(curAssertion.op2.vn)); - if ((cnstLimit == 0) && (curAssertion->assertionKind == Compiler::OAK_NOT_EQUAL) && canUseCheckedBounds && - comp->vnStore->IsVNCheckedBound(curAssertion->op1.vn)) + if ((cnstLimit == 0) && (curAssertion.assertionKind == Compiler::OAK_NOT_EQUAL) && canUseCheckedBounds && + comp->vnStore->IsVNCheckedBound(curAssertion.op1.vn)) { // we have arr.Len != 0, so the length must be atleast one limit = Limit(Limit::keConstant, 1); cmpOper = GT_GE; } - else if (curAssertion->assertionKind == Compiler::OAK_EQUAL) + else if (curAssertion.assertionKind == Compiler::OAK_EQUAL) { limit = Limit(Limit::keConstant, cnstLimit); cmpOper = GT_EQ; } else { - assert(curAssertion->assertionKind == Compiler::OAK_NOT_EQUAL); + assert(curAssertion.assertionKind == Compiler::OAK_NOT_EQUAL); // We have an assertion of the form "X != constLimit". // For example, if the assertion is "X != 100" and the current range for X is [100, X], @@ -1039,10 +1039,10 @@ void RangeCheck::MergeEdgeAssertions(Compiler* comp, isConstantAssertion = true; } // Current assertion asserts a bounds check does not throw - else if (curAssertion->IsBoundsCheckNoThrow()) + else if (curAssertion.IsBoundsCheckNoThrow()) { - ValueNum indexVN = curAssertion->op1.bnd.vnIdx; - ValueNum lenVN = curAssertion->op1.bnd.vnLen; + ValueNum indexVN = curAssertion.op1.bnd.vnIdx; + ValueNum lenVN = curAssertion.op1.bnd.vnLen; if (normalLclVN == indexVN) { if (canUseCheckedBounds) @@ -1099,15 +1099,15 @@ void RangeCheck::MergeEdgeAssertions(Compiler* comp, assert(limit.IsBinOpArray() || limit.IsConstant()); // Make sure the assertion is of the form != 0 or == 0 if it isn't a constant assertion. - if (!isConstantAssertion && (curAssertion->assertionKind != Compiler::OAK_NO_THROW) && - (curAssertion->op2.vn != comp->vnStore->VNZeroForType(TYP_INT))) + if (!isConstantAssertion && (curAssertion.assertionKind != Compiler::OAK_NO_THROW) && + (curAssertion.op2.vn != comp->vnStore->VNZeroForType(TYP_INT))) { continue; } #ifdef DEBUG if (comp->verbose) { - comp->optPrintAssertion(*curAssertion, assertionIndex); + comp->optPrintAssertion(curAssertion, assertionIndex); } #endif @@ -1149,7 +1149,7 @@ void RangeCheck::MergeEdgeAssertions(Compiler* comp, // If we have a non-constant assertion of the form == 0 (i.e., equals false), then reverse relop. // The relop has to be reversed because we have: (i < length) is false which is the same // as (i >= length). - if ((curAssertion->assertionKind == Compiler::OAK_EQUAL) && !isConstantAssertion) + if ((curAssertion.assertionKind == Compiler::OAK_EQUAL) && !isConstantAssertion) { cmpOper = GenTree::ReverseRelop(cmpOper); } From 96ec5e50908f90d15a1e39d309c4af59c7bf263e Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 1 Feb 2026 00:29:17 +0100 Subject: [PATCH 11/17] clean up --- src/coreclr/jit/assertionprop.cpp | 253 +++++++++++++++--------------- src/coreclr/jit/compiler.h | 53 ++++--- 2 files changed, 159 insertions(+), 147 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 11abb224be402c..4313be5840dcf8 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1148,6 +1148,8 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ // Set op1 to the instance pointer of the indirection op1 = op1->gtEffectiveVal(); + // TODO-Cleanup: Replace with gtPeelOffset with proper fgBigOffset check + // It will produce a few regressions. ssize_t offset = 0; while (op1->OperIs(GT_ADD) && op1->TypeIs(TYP_BYREF)) { @@ -1199,165 +1201,166 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ return NO_ASSERTION_INDEX; } + /* Skip over a GT_COMMA node(s), if necessary */ + while (op2->OperIs(GT_COMMA)) { - /* Skip over a GT_COMMA node(s), if necessary */ - while (op2->OperIs(GT_COMMA)) + op2 = op2->AsOp()->gtOp2; + } + + switch (op2->OperGet()) + { + // + // Constant Assertions + // + case GT_CNS_DBL: { - op2 = op2->AsOp()->gtOp2; + double dblCns = op2->AsDblCon()->DconValue(); + if (FloatingPointUtils::isNaN(dblCns)) + { + return NO_ASSERTION_INDEX; + } + + ValueNum op1VN = optConservativeNormalVN(op1); + ValueNum op2VN = optConservativeNormalVN(op2); + if (!optLocalAssertionProp && (op1VN == ValueNumStore::NoVN || op2VN == ValueNumStore::NoVN)) + { + // GlobalAP requires valid VNs. + return NO_ASSERTION_INDEX; + } + + AssertionDsc dsc = AssertionDsc::CreateConstLclVarAssertion(this, lclNum, op1VN, dblCns, op2VN, equals); + return optAddAssertion(dsc); } - switch (op2->gtOper) + case GT_CNS_INT: { - // - // Constant Assertions - // - case GT_CNS_DBL: + ValueNum op1VN = optConservativeNormalVN(op1); + ValueNum op2VN = optConservativeNormalVN(op2); + if (!optLocalAssertionProp && (op1VN == ValueNumStore::NoVN || op2VN == ValueNumStore::NoVN)) { - if (FloatingPointUtils::isNaN(op2->AsDblCon()->DconValue())) - { - return NO_ASSERTION_INDEX; - } + return NO_ASSERTION_INDEX; + } - ValueNum op1VN = optConservativeNormalVN(op1); - ValueNum op2VN = optConservativeNormalVN(op2); - if (!optLocalAssertionProp && (op1VN == ValueNumStore::NoVN || op2VN == ValueNumStore::NoVN)) - { - return NO_ASSERTION_INDEX; - } - AssertionDsc dsc = - AssertionDsc::CreateConstantLclvarAssertion(this, lclNum, op1VN, op2->AsDblCon()->DconValue(), - op2VN, equals); + ssize_t iconVal = op2->AsIntCon()->IconValue(); + if (op1->TypeIs(TYP_STRUCT)) + { + assert(iconVal == 0); + AssertionDsc dsc = AssertionDsc::CreateConstLclVarAssertion(this, lclNum, op1VN, 0, op2VN, equals); + dsc.op2.kind = O2K_ZEROOBJ; return optAddAssertion(dsc); } - case GT_CNS_INT: + if (varTypeIsSmall(lclVar)) { - ValueNum op1VN = optConservativeNormalVN(op1); - ValueNum op2VN = optConservativeNormalVN(op2); - if (!optLocalAssertionProp && (op1VN == ValueNumStore::NoVN || op2VN == ValueNumStore::NoVN)) + ssize_t truncatedIconVal = optCastConstantSmall(iconVal, lclVar->TypeGet()); + if (!op1->OperIs(GT_STORE_LCL_VAR) && (truncatedIconVal != iconVal)) { + // This assertion would be saying that a small local is equal to a value + // outside its range. It means this block is unreachable. Avoid creating + // such impossible assertions which can hit assertions in other places. return NO_ASSERTION_INDEX; } - if (op1->TypeIs(TYP_STRUCT)) + iconVal = truncatedIconVal; + if (!optLocalAssertionProp) { - AssertionDsc dsc = - AssertionDsc::CreateConstantLclvarAssertion(this, lclNum, op1VN, 0, op2VN, equals); - dsc.op2.kind = O2K_ZEROOBJ; - return optAddAssertion(dsc); + op2VN = vnStore->VNForIntCon(static_cast(iconVal)); } + } - ssize_t iconVal = op2->AsIntCon()->IconValue(); - if (varTypeIsSmall(lclVar)) - { - ssize_t truncatedIconVal = optCastConstantSmall(iconVal, lclVar->TypeGet()); - if (!op1->OperIs(GT_STORE_LCL_VAR) && (truncatedIconVal != iconVal)) - { - // This assertion would be saying that a small local is equal to a value - // outside its range. It means this block is unreachable. Avoid creating - // such impossible assertions which can hit assertions in other places. - return NO_ASSERTION_INDEX; - } - - iconVal = truncatedIconVal; - if (!optLocalAssertionProp) - { - op2VN = vnStore->VNForIntCon(static_cast(iconVal)); - } - } + AssertionDsc dsc = + AssertionDsc::CreateConstLclVarAssertion(this, lclNum, op1VN, iconVal, op2VN, equals); - AssertionDsc dsc = - AssertionDsc::CreateConstantLclvarAssertion(this, lclNum, op1VN, iconVal, op2VN, equals); - dsc.op2.SetIconFlag(op2->GetIconHandleFlag(), op2->AsIntCon()->gtFieldSeq); - return optAddAssertion(dsc); - } + // Attach the handle flag with FieldSeq if any. + dsc.op2.SetIconFlag(op2->GetIconHandleFlag(), op2->AsIntCon()->gtFieldSeq); + return optAddAssertion(dsc); + } - case GT_LCL_VAR: + case GT_LCL_VAR: + { + if (!optLocalAssertionProp) { - if (!optLocalAssertionProp) - { - // O2K_LCLVAR_COPY is local assertion prop only - return NO_ASSERTION_INDEX; - } - - unsigned lclNum2 = op2->AsLclVarCommon()->GetLclNum(); - LclVarDsc* lclVar2 = lvaGetDesc(lclNum2); + // O2K_LCLVAR_COPY is local assertion prop only + return NO_ASSERTION_INDEX; + } - // If the two locals are the same then bail - if (lclNum == lclNum2) - { - return NO_ASSERTION_INDEX; - } + unsigned lclNum2 = op2->AsLclVarCommon()->GetLclNum(); + LclVarDsc* lclVar2 = lvaGetDesc(lclNum2); - // If the types are different then bail */ - if (lclVar->lvType != lclVar2->lvType) - { - return NO_ASSERTION_INDEX; - } - - // If we're making a copy of a "normalize on load" lclvar then the destination - // has to be "normalize on load" as well, otherwise we risk skipping normalization. - if (lclVar2->lvNormalizeOnLoad() && !lclVar->lvNormalizeOnLoad()) - { - return NO_ASSERTION_INDEX; - } + // If the two locals are the same then bail + if (lclNum == lclNum2) + { + return NO_ASSERTION_INDEX; + } - // If the local variable has its address exposed then bail - if (lclVar2->IsAddressExposed()) - { - return NO_ASSERTION_INDEX; - } + // If the types are different then bail */ + if (lclVar->lvType != lclVar2->lvType) + { + return NO_ASSERTION_INDEX; + } - // We process locals when we see the LCL_VAR node instead - // of at its actual use point (its parent). That opens us - // up to problems in a case like the following, assuming we - // allowed creating an assertion like V10 = V35: - // - // └──▌ ADD int - // ├──▌ LCL_VAR int V10 tmp6 -> copy propagated to [V35 tmp31] - // └──▌ COMMA int - // ├──▌ STORE_LCL_VAR int V35 tmp31 - // │ └──▌ LCL_FLD int V03 loc1 [+4] - if (lclVar2->lvRedefinedInEmbeddedStatement) - { - return NO_ASSERTION_INDEX; - } + // If we're making a copy of a "normalize on load" lclvar then the destination + // has to be "normalize on load" as well, otherwise we risk skipping normalization. + if (lclVar2->lvNormalizeOnLoad() && !lclVar->lvNormalizeOnLoad()) + { + return NO_ASSERTION_INDEX; + } - // Ok everything has been set and the assertion looks good - AssertionDsc assertion = AssertionDsc::CreateLclvarCopy(this, lclNum, lclNum2, equals); - return optAddAssertion(assertion); + // If the local variable has its address exposed then bail + if (lclVar2->IsAddressExposed()) + { + return NO_ASSERTION_INDEX; } - case GT_CALL: + // We process locals when we see the LCL_VAR node instead + // of at its actual use point (its parent). That opens us + // up to problems in a case like the following, assuming we + // allowed creating an assertion like V10 = V35: + // + // └──▌ ADD int + // ├──▌ LCL_VAR int V10 tmp6 -> copy propagated to [V35 tmp31] + // └──▌ COMMA int + // ├──▌ STORE_LCL_VAR int V35 tmp31 + // │ └──▌ LCL_FLD int V03 loc1 [+4] + if (lclVar2->lvRedefinedInEmbeddedStatement) { - if (optLocalAssertionProp) - { - GenTreeCall* const call = op2->AsCall(); - if (call->IsHelperCall() && s_helperCallProperties.NonNullReturn(call->GetHelperNum())) - { - AssertionDsc assertion = AssertionDsc::CreateLclNonNullAssertion(this, lclNum); - return optAddAssertion(assertion); - } - } - break; + return NO_ASSERTION_INDEX; } - default: - break; + // Ok everything has been set and the assertion looks good + AssertionDsc assertion = AssertionDsc::CreateLclvarCopy(this, lclNum, lclNum2, equals); + return optAddAssertion(assertion); } - // Try and see if we can make a subrange assertion. - if (optLocalAssertionProp && equals && varTypeIsIntegral(op2)) + case GT_CALL: { - IntegralRange nodeRange = IntegralRange::ForNode(op2, this); - IntegralRange typeRange = IntegralRange::ForType(genActualType(op2)); - assert(typeRange.Contains(nodeRange)); - - if (!typeRange.Equals(nodeRange)) + if (optLocalAssertionProp) { - AssertionDsc assertion = AssertionDsc::CreateSubrange(this, lclNum, nodeRange); - return optAddAssertion(assertion); + GenTreeCall* const call = op2->AsCall(); + if (call->IsHelperCall() && s_helperCallProperties.NonNullReturn(call->GetHelperNum())) + { + AssertionDsc assertion = AssertionDsc::CreateLclNonNullAssertion(this, lclNum); + return optAddAssertion(assertion); + } } + break; + } + + default: + break; + } + + // Try and see if we can make a subrange assertion. + if (optLocalAssertionProp && equals && varTypeIsIntegral(op2)) + { + IntegralRange nodeRange = IntegralRange::ForNode(op2, this); + IntegralRange typeRange = IntegralRange::ForType(genActualType(op2)); + assert(typeRange.Contains(nodeRange)); + + if (!typeRange.Equals(nodeRange)) + { + AssertionDsc assertion = AssertionDsc::CreateSubrange(this, lclNum, nodeRange); + return optAddAssertion(assertion); } } } @@ -1454,7 +1457,7 @@ void Compiler::optPrintVnAssertionMapping() const * about that VN. Given "assertions" about a "vn" add it to the previously * mapped assertions about that "vn." */ -void Compiler::optAddVnAssertionMapping(ValueNum vn, AssertionIndex index) const +void Compiler::optAddVnAssertionMapping(ValueNum vn, AssertionIndex index) { ASSERT_TP* cur = optValueNumToAsserts->LookupPointer(vn); if (cur == nullptr) @@ -5533,7 +5536,7 @@ bool Compiler::optCreateJumpTableImpliedAssertions(BasicBlock* switchBb) // TODO-Cleanup: Should use O1K_VN instead of O1K_LCLVAR ValueNum valueVN = vnStore->VNForIntCon(value); newAssertIdx = optAddAssertion( - AssertionDsc::CreateConstantLclvarAssertion(this, BAD_VAR_NUM, opVN, value, valueVN, true)); + AssertionDsc::CreateConstLclVarAssertion(this, BAD_VAR_NUM, opVN, value, valueVN, true)); } if (newAssertIdx.HasAssertion()) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 802183d9c536a9..56ee74a00eb6c0 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7986,19 +7986,25 @@ class Compiler bool Equals(const AssertionDsc& that, bool vnBased) const { - if (assertionKind != that.assertionKind) + // op1.kind check is implied by HasSameOp1, but it improves performance to do it first. + if (op1.kind != that.op1.kind || assertionKind != that.assertionKind) { return false; } - else if (assertionKind == OAK_NO_THROW) + + if (!HasSameOp1(that, vnBased)) { - assert(op2.kind == O2K_INVALID); - return HasSameOp1(that, vnBased); + return false; } - else + + if (assertionKind != OAK_NO_THROW) { - return HasSameOp1(that, vnBased) && HasSameOp2(that, vnBased); + return HasSameOp2(that, vnBased); } + + // OAK_NO_THROW is the only kind of assertion where op2 is unused. + assert(op2.kind == O2K_INVALID); + return true; } // @@ -8007,8 +8013,8 @@ class Compiler // Create a generic "lclNum ==/!= constant" or "vn ==/!= constant" assertion template - static AssertionDsc CreateConstantLclvarAssertion( - Compiler* comp, unsigned lclNum, ValueNum vn, T cns, ValueNum cnsVN, bool equals) + static AssertionDsc CreateConstLclVarAssertion( + const Compiler* comp, unsigned lclNum, ValueNum vn, T cns, ValueNum cnsVN, bool equals) { AssertionDsc dsc = {}; dsc.assertionKind = equals ? OAK_EQUAL : OAK_NOT_EQUAL; @@ -8056,21 +8062,23 @@ class Compiler } // Create "lclNum != null" assertion - static AssertionDsc CreateLclNonNullAssertion(Compiler* comp, unsigned lclNum) + static AssertionDsc CreateLclNonNullAssertion(const Compiler* comp, unsigned lclNum) { - return CreateConstantLclvarAssertion(comp, lclNum, ValueNumStore::NoVN, 0, ValueNumStore::VNForNull(), - /*equals*/ false); + assert(comp->optLocalAssertionProp); + return CreateConstLclVarAssertion(comp, lclNum, ValueNumStore::NoVN, 0, ValueNumStore::VNForNull(), + /*equals*/ false); } // Create "vn != null" assertion - static AssertionDsc CreateVNNonNullAssertion(Compiler* comp, ValueNum vn) + static AssertionDsc CreateVNNonNullAssertion(const Compiler* comp, ValueNum vn) { - return CreateConstantLclvarAssertion(comp, BAD_VAR_NUM, vn, 0, ValueNumStore::VNForNull(), - /*equals*/ false); + assert(!comp->optLocalAssertionProp); + return CreateConstLclVarAssertion(comp, BAD_VAR_NUM, vn, 0, ValueNumStore::VNForNull(), + /*equals*/ false); } // Create "lclNum1 ==/!= lclNum2" copy assertion - static AssertionDsc CreateLclvarCopy(Compiler* comp, unsigned lclNum1, unsigned lclNum2, bool equals) + static AssertionDsc CreateLclvarCopy(const Compiler* comp, unsigned lclNum1, unsigned lclNum2, bool equals) { assert(comp->optLocalAssertionProp); assert(lclNum1 != BAD_VAR_NUM); @@ -8088,7 +8096,7 @@ class Compiler } // Create "lclNum in range [lowerBound, upperBound]" assertion - static AssertionDsc CreateSubrange(Compiler* comp, unsigned lclNum, const IntegralRange& range) + static AssertionDsc CreateSubrange(const Compiler* comp, unsigned lclNum, const IntegralRange& range) { assert(comp->optLocalAssertionProp); assert(lclNum != BAD_VAR_NUM); @@ -8105,7 +8113,7 @@ class Compiler } // Create "VN ==/!= int32_constant" assertion - static AssertionDsc CreateInt32ConstantVNAssertion(Compiler* comp, ValueNum op1VN, ValueNum op2VN, bool equals) + static AssertionDsc CreateInt32ConstantVNAssertion(const Compiler* comp, ValueNum op1VN, ValueNum op2VN, bool equals) { assert(op1VN != ValueNumStore::NoVN); assert(op2VN != ValueNumStore::NoVN); @@ -8126,7 +8134,7 @@ class Compiler } // Create an exact-type or sub-type assertion: objVN is (exactly of | subtype of) typeHndVN - static AssertionDsc CreateSubtype(Compiler* comp, ValueNum objVN, ValueNum typeHndVN, bool exact) + static AssertionDsc CreateSubtype(const Compiler* comp, ValueNum objVN, ValueNum typeHndVN, bool exact) { assert((objVN != ValueNumStore::NoVN) && comp->vnStore->IsVNTypeHandle(typeHndVN)); @@ -8142,7 +8150,7 @@ class Compiler } // Create a no-throw bounds check assertion: idxVN u< lenVN - static AssertionDsc CreateNoThrowArrBnd(Compiler* comp, ValueNum idxVN, ValueNum lenVN) + static AssertionDsc CreateNoThrowArrBnd(const Compiler* comp, ValueNum idxVN, ValueNum lenVN) { assert(idxVN != ValueNumStore::NoVN); assert(lenVN != ValueNumStore::NoVN); @@ -8152,11 +8160,12 @@ class Compiler dsc.op1.kind = O1K_ARR_BND; dsc.op1.bnd.vnIdx = idxVN; dsc.op1.bnd.vnLen = lenVN; + dsc.op2.kind = O2K_INVALID; return dsc; } // Create "i < bnd +/- k != 0" or just "i < bnd != 0" assertion - static AssertionDsc CreateCompareCheckedBoundArith(Compiler* comp, ValueNum relopVN, bool withArith) + static AssertionDsc CreateCompareCheckedBoundArith(const Compiler* comp, ValueNum relopVN, bool withArith) { assert(relopVN != ValueNumStore::NoVN); @@ -8185,7 +8194,7 @@ class Compiler // Create "i < constant" or "i u< constant" assertion // TODO-Cleanup: Rename it as it's not necessarily a loop bound - static AssertionDsc CreateConstantLoopBound(Compiler* comp, ValueNum relopVN, bool isUnsigned) + static AssertionDsc CreateConstantLoopBound(const Compiler* comp, ValueNum relopVN, bool isUnsigned) { assert(relopVN != ValueNumStore::NoVN); if (isUnsigned) @@ -8293,7 +8302,7 @@ class Compiler bool optAssertionVnInvolvesNan(const AssertionDsc& assertion) const; AssertionIndex optAddAssertion(const AssertionDsc& assertion); - void optAddVnAssertionMapping(ValueNum vn, AssertionIndex index) const; + void optAddVnAssertionMapping(ValueNum vn, AssertionIndex index); #ifdef DEBUG void optPrintVnAssertionMapping() const; #endif From 4c57d164e5c965094003879d1c6a966d7298ee6d Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 1 Feb 2026 00:48:40 +0100 Subject: [PATCH 12/17] remove quirk --- src/coreclr/jit/compiler.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 56ee74a00eb6c0..ce19c93fc1ff1b 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7986,8 +7986,7 @@ class Compiler bool Equals(const AssertionDsc& that, bool vnBased) const { - // op1.kind check is implied by HasSameOp1, but it improves performance to do it first. - if (op1.kind != that.op1.kind || assertionKind != that.assertionKind) + if (assertionKind != that.assertionKind) { return false; } From 938bd4589b1fc7dba55f004d9042c93daaafd88d Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 1 Feb 2026 00:53:21 +0100 Subject: [PATCH 13/17] FB --- src/coreclr/jit/compiler.h | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index ce19c93fc1ff1b..a0b5fa0a19f0eb 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8042,7 +8042,6 @@ class Compiler dsc.op2.vn = cnsVN; } - dsc.op2.SetIconFlag(GTF_EMPTY); if constexpr (std::is_same_v || std::is_same_v) { dsc.op2.kind = O2K_CONST_INT; @@ -8112,7 +8111,10 @@ class Compiler } // Create "VN ==/!= int32_constant" assertion - static AssertionDsc CreateInt32ConstantVNAssertion(const Compiler* comp, ValueNum op1VN, ValueNum op2VN, bool equals) + static AssertionDsc CreateInt32ConstantVNAssertion(const Compiler* comp, + ValueNum op1VN, + ValueNum op2VN, + bool equals) { assert(op1VN != ValueNumStore::NoVN); assert(op2VN != ValueNumStore::NoVN); @@ -8128,7 +8130,6 @@ class Compiler dsc.op1.kind = O1K_VN; dsc.op2.kind = O2K_CONST_INT; dsc.op2.u1.iconVal = comp->vnStore->ConstantValue(op2VN); - dsc.op2.SetIconFlag(GTF_EMPTY); return dsc; } @@ -8187,7 +8188,6 @@ class Compiler dsc.op2.kind = O2K_CONST_INT; dsc.op2.vn = comp->vnStore->VNZeroForType(TYP_INT); dsc.op2.u1.iconVal = 0; - dsc.op2.SetIconFlag(GTF_EMPTY); return dsc; } @@ -8216,7 +8216,6 @@ class Compiler dsc.op2.kind = O2K_CONST_INT; dsc.op2.vn = comp->vnStore->VNZeroForType(TYP_INT); dsc.op2.u1.iconVal = 0; - dsc.op2.SetIconFlag(GTF_EMPTY); return dsc; } }; From 1a0aa887a500ee9528c9382c192b6f63d1d8af4c Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 1 Feb 2026 01:00:19 +0100 Subject: [PATCH 14/17] cleanup --- src/coreclr/jit/assertionprop.cpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 4313be5840dcf8..f985b8c542a2f0 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1827,9 +1827,8 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) { return NO_ASSERTION_INDEX; } - ValueNum relopVN = vnStore->VNConservativeNormalValue(relop->gtVNPair); - ValueNumStore::UnsignedCompareCheckedBoundInfo unsignedCompareBnd; + ValueNum relopVN = vnStore->VNConservativeNormalValue(relop->gtVNPair); // Cases where op1 holds the lhs of the condition and op2 holds the bound arithmetic. // Loop condition like: "i < bnd +/-k" @@ -1841,19 +1840,22 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) optCreateComplementaryAssertion(index, nullptr, nullptr); return index; } + // Cases where op1 holds the lhs of the condition op2 holds the bound. // Loop condition like "i < bnd" // Assertion: "i < bnd != 0" - else if (vnStore->IsVNCompareCheckedBound(relopVN)) + if (vnStore->IsVNCompareCheckedBound(relopVN)) { AssertionDsc dsc = AssertionDsc::CreateCompareCheckedBoundArith(this, relopVN, /*withArith*/ false); AssertionIndex index = optAddAssertion(dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; } + // Loop condition like "(uint)i < (uint)bnd" or equivalent // Assertion: "no throw" since this condition guarantees that i is both >= 0 and < bnd (on the appropriate edge) - else if (vnStore->IsVNUnsignedCompareCheckedBound(relopVN, &unsignedCompareBnd)) + ValueNumStore::UnsignedCompareCheckedBoundInfo unsignedCompareBnd; + if (vnStore->IsVNUnsignedCompareCheckedBound(relopVN, &unsignedCompareBnd)) { ValueNum idxVN = vnStore->VNNormalValue(unsignedCompareBnd.vnIdx); ValueNum lenVN = vnStore->VNNormalValue(unsignedCompareBnd.vnBound); @@ -1868,23 +1870,28 @@ AssertionInfo Compiler::optCreateJTrueBoundsAssertion(GenTree* tree) } return index; } + // Cases where op1 holds the lhs of the condition op2 holds rhs. // Loop condition like "i < 100" // Assertion: "i < 100 != 0" - else if (vnStore->IsVNConstantBound(relopVN)) + if (vnStore->IsVNConstantBound(relopVN)) { AssertionDsc dsc = AssertionDsc::CreateConstantLoopBound(this, relopVN, /*isUnsigned*/ false); AssertionIndex index = optAddAssertion(dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; } - else if (vnStore->IsVNConstantBoundUnsigned(relopVN)) + + // Same as above but for unsigned comparisons. + // Assertion: "i u< 100 != 0" + if (vnStore->IsVNConstantBoundUnsigned(relopVN)) { AssertionDsc dsc = AssertionDsc::CreateConstantLoopBound(this, relopVN, /*isUnsigned*/ true); AssertionIndex index = optAddAssertion(dsc); optCreateComplementaryAssertion(index, nullptr, nullptr); return index; } + return NO_ASSERTION_INDEX; } @@ -1900,8 +1907,6 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) return NO_ASSERTION_INDEX; } - bool equals = true; - AssertionInfo info = optCreateJTrueBoundsAssertion(tree); if (info.HasAssertion()) { @@ -1914,6 +1919,7 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) } // Find assertion kind. + bool equals; switch (relop->gtOper) { case GT_EQ: @@ -2043,10 +2049,9 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) // Also note The CASTCLASS helpers won't appear in predicates as they throw on failure. // So the helper list here is smaller than the one in optAssertionProp_Call. // - if ((call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_ISINSTANCEOFINTERFACE)) || - (call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_ISINSTANCEOFARRAY)) || - (call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_ISINSTANCEOFCLASS)) || - (call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_ISINSTANCEOFANY))) + CorInfoHelpFunc helper = eeGetHelperNum(call->gtCallMethHnd); + if ((helper == CORINFO_HELP_ISINSTANCEOFINTERFACE) || (helper == CORINFO_HELP_ISINSTANCEOFARRAY) || + (helper == CORINFO_HELP_ISINSTANCEOFCLASS) || (helper == CORINFO_HELP_ISINSTANCEOFANY)) { GenTree* objectNode = call->gtArgs.GetUserArgByIndex(1)->GetNode(); GenTree* methodTableNode = call->gtArgs.GetUserArgByIndex(0)->GetNode(); From bfdd6b9c6a4cc60b3c69645cffa150e651329c9e Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 1 Feb 2026 01:25:15 +0100 Subject: [PATCH 15/17] Cleanup --- src/coreclr/jit/assertionprop.cpp | 40 ++----------------------------- src/coreclr/jit/compiler.h | 14 +++++++---- 2 files changed, 11 insertions(+), 43 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index f985b8c542a2f0..35692a9bc3840a 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1520,37 +1520,6 @@ AssertionIndex Compiler::optAddAssertion(const AssertionDsc& newAssertion) return NO_ASSERTION_INDEX; } - if (!optLocalAssertionProp) - { - // Ignore VN-based assertions with NoVN - switch (newAssertion.op1.kind) - { - case O1K_LCLVAR: - case O1K_VN: - case O1K_BOUND_OPER_BND: - case O1K_BOUND_LOOP_BND: - case O1K_CONSTANT_LOOP_BND: - case O1K_CONSTANT_LOOP_BND_UN: - case O1K_EXACT_TYPE: - case O1K_SUBTYPE: - if (newAssertion.op1.vn == ValueNumStore::NoVN) - { - return NO_ASSERTION_INDEX; - } - break; - case O1K_ARR_BND: - if ((newAssertion.op1.bnd.vnIdx == ValueNumStore::NoVN) || - (newAssertion.op1.bnd.vnLen == ValueNumStore::NoVN)) - { - return NO_ASSERTION_INDEX; - } - break; - - default: - break; - } - } - // See if we already have this assertion in the table. // // For local assertion prop we can speed things up by checking the dep vector. @@ -2272,9 +2241,8 @@ AssertionIndex Compiler::optFindComplementary(AssertionIndex assertIndex) // AssertionIndex Compiler::optAssertionIsSubrange(GenTree* tree, IntegralRange range, ASSERT_VALARG_TP assertions) { - if (!optCanPropSubRange) + if (!optCanPropSubRange || !optLocalAssertionProp) { - // (don't early out in checked, verify above) return NO_ASSERTION_INDEX; } @@ -2286,11 +2254,7 @@ AssertionIndex Compiler::optAssertionIsSubrange(GenTree* tree, IntegralRange ran const AssertionDsc& curAssertion = optGetAssertion(index); if (curAssertion.CanPropSubRange()) { - // For local assertion prop use comparison on locals, and use comparison on vns for global prop. - bool isEqual = optLocalAssertionProp - ? (curAssertion.op1.lclNum == tree->AsLclVarCommon()->GetLclNum()) - : (curAssertion.op1.vn == vnStore->VNConservativeNormalValue(tree->gtVNPair)); - if (!isEqual) + if (curAssertion.op1.lclNum != tree->AsLclVarCommon()->GetLclNum()) { continue; } diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index a0b5fa0a19f0eb..2b7671947a37b5 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8022,12 +8022,15 @@ class Compiler if (comp->optLocalAssertionProp) { assert(lclNum != BAD_VAR_NUM); + + // TODO-Cleanup: We need to introduce getters for op1.vn and op2.vn that validate that we don't use + // these outside of their intended context (only certain kinds of assertions and only in global-AP). dsc.op1.vn = ValueNumStore::NoVN; dsc.op2.vn = ValueNumStore::NoVN; dsc.op1.lclNum = lclNum; // TODO-Quirk: Somewhere in local-AP we check op2.vn for being ValueNumStore::VNForNull - // while we shouldn't. Remove it. + // while we shouldn't. It is left here for zero-diff with previous behavior. if (cnsVN == ValueNumStore::VNForNull()) { dsc.op2.vn = ValueNumStore::VNForNull(); @@ -8037,7 +8040,7 @@ class Compiler { assert(vn != ValueNumStore::NoVN); assert(cnsVN != ValueNumStore::NoVN); - dsc.op1.lclNum = BAD_VAR_NUM; + dsc.op1.lclNum = BAD_VAR_NUM; // same as above dsc.op1.vn = vn; dsc.op2.vn = cnsVN; } @@ -8090,6 +8093,10 @@ class Compiler dsc.op2.vn = ValueNumStore::NoVN; dsc.op2.lclNum = lclNum2; dsc.op2.kind = O2K_LCLVAR_COPY; + + // TODO-Cleanup: We need to introduce getters for op1.vn and op2.vn that validate that we don't use these + // outside of their intended context (only certain kinds of assertions and only in global-AP). + return dsc; } @@ -8102,9 +8109,7 @@ class Compiler AssertionDsc dsc = {}; dsc.assertionKind = OAK_SUBRANGE; dsc.op1.kind = O1K_LCLVAR; - dsc.op1.vn = ValueNumStore::NoVN; dsc.op1.lclNum = lclNum; - dsc.op2.vn = ValueNumStore::NoVN; dsc.op2.kind = O2K_SUBRANGE; dsc.op2.u2 = range; return dsc; @@ -8124,7 +8129,6 @@ class Compiler AssertionDsc dsc = {}; dsc.assertionKind = equals ? OAK_EQUAL : OAK_NOT_EQUAL; - dsc.op1.lclNum = BAD_VAR_NUM; dsc.op1.vn = op1VN; dsc.op2.vn = op2VN; dsc.op1.kind = O1K_VN; From 5b80326712beeaf5261caf54fc4fe5ff81965cfc Mon Sep 17 00:00:00 2001 From: EgorBo Date: Sun, 1 Feb 2026 01:53:39 +0100 Subject: [PATCH 16/17] more asserts --- src/coreclr/jit/assertionprop.cpp | 15 +++++++++------ src/coreclr/jit/compiler.h | 22 ++++++++-------------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 35692a9bc3840a..725815b4dab3c4 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -2211,14 +2211,17 @@ AssertionIndex Compiler::optFindComplementary(AssertionIndex assertIndex) return index; } - for (AssertionIndex index = 1; index <= optAssertionCount; ++index) + if (!optLocalAssertionProp) // Seems to be not profitable for Local-AP { - // Make sure assertion kinds are complementary and op1, op2 kinds match. - const AssertionDsc& curAssertion = optGetAssertion(index); - if (curAssertion.Complementary(inputAssertion, !optLocalAssertionProp)) + for (AssertionIndex index = 1; index <= optAssertionCount; ++index) { - optMapComplementary(assertIndex, index); - return index; + // Make sure assertion kinds are complementary and op1, op2 kinds match. + const AssertionDsc& curAssertion = optGetAssertion(index); + if (curAssertion.Complementary(inputAssertion, !optLocalAssertionProp)) + { + optMapComplementary(assertIndex, index); + return index; + } } } return NO_ASSERTION_INDEX; diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 2b7671947a37b5..2de9a215d0206f 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7784,8 +7784,8 @@ class Compiler }; struct AssertionDscOp1 { - optOp1Kind kind; // a normal LclVar, or Exact-type or Subtype - ValueNum vn; + optOp1Kind kind; + ValueNum vn; // TODO-Cleanup: move this field to the union below as they are not used together union { unsigned lclNum; @@ -7794,14 +7794,14 @@ class Compiler } op1; struct AssertionDscOp2 { - optOp2Kind kind; // a const or copy assertion + optOp2Kind kind; private: uint16_t m_encodedIconFlags; // encoded icon gtFlags, don't use directly public: ValueNum vn; struct IntVal { - ssize_t iconVal; // integer + ssize_t iconVal; FieldSeq* fieldSeq; }; union @@ -7814,11 +7814,13 @@ class Compiler bool HasIconFlag() const { + assert(kind == O2K_CONST_INT); assert(m_encodedIconFlags <= 0xFF); return m_encodedIconFlags != 0; } GenTreeFlags GetIconFlag() const { + assert(kind == O2K_CONST_INT); // number of trailing zeros in GTF_ICON_HDL_MASK const uint16_t iconMaskTzc = 24; static_assert((0xFF000000 == GTF_ICON_HDL_MASK) && (GTF_ICON_HDL_MASK >> iconMaskTzc) == 0xFF); @@ -7829,6 +7831,7 @@ class Compiler } void SetIconFlag(GenTreeFlags flags, FieldSeq* fieldSeq = nullptr) { + assert(kind == O2K_CONST_INT); const uint16_t iconMaskTzc = 24; assert((flags & ~GTF_ICON_HDL_MASK) == 0); m_encodedIconFlags = flags >> iconMaskTzc; @@ -7901,11 +7904,6 @@ class Compiler assertionKind = assertionKind == OAK_EQUAL ? OAK_NOT_EQUAL : OAK_EQUAL; } - static bool SameKind(const AssertionDsc& a1, const AssertionDsc& a2) - { - return a1.assertionKind == a2.assertionKind && a1.op1.kind == a2.op1.kind && a1.op2.kind == a2.op2.kind; - } - static bool ComplementaryKind(optAssertionKind kind, optAssertionKind kind2) { if (kind == OAK_EQUAL) @@ -7967,9 +7965,6 @@ class Compiler return op2.u2.Equals(that.op2.u2); case O2K_INVALID: - // we will return false - break; - default: assert(!"Unexpected value for op2.kind in AssertionDsc."); break; @@ -8148,8 +8143,7 @@ class Compiler dsc.op2.kind = O2K_CONST_INT; dsc.op2.u1.iconVal = comp->vnStore->CoercedConstantValue(typeHndVN); dsc.op2.vn = typeHndVN; - dsc.op2.SetIconFlag(GTF_ICON_CLASS_HDL); - dsc.assertionKind = OAK_EQUAL; + dsc.assertionKind = OAK_EQUAL; return dsc; } From d784ed0fd99e4c38da1a9012f94416610eb6dad3 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Mon, 2 Feb 2026 00:06:00 +0100 Subject: [PATCH 17/17] Address feedback --- src/coreclr/jit/assertionprop.cpp | 52 ++++++++++++++++--------------- src/coreclr/jit/compiler.h | 11 ++++--- src/coreclr/jit/compiler.hpp | 38 +++++++--------------- src/coreclr/jit/morph.cpp | 2 +- 4 files changed, 46 insertions(+), 57 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 725815b4dab3c4..89eb34bdd3770d 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -598,18 +598,23 @@ bool IntegralRange::Contains(int64_t value) const // GetAssertionDep: Retrieve the assertions on this local variable // // Arguments: -// lclNum - The local var id. +// lclNum - The local var id. +// mustExist - If true, assert that the dependent assertions must exist. // // Return Value: // The dependent assertions (assertions using the value of the local var) // of the local var. // -ASSERT_TP& Compiler::GetAssertionDep(unsigned lclNum) +ASSERT_TP& Compiler::GetAssertionDep(unsigned lclNum, bool mustExist) { JitExpandArray& dep = *optAssertionDep; if (dep[lclNum] == nullptr) { + if (mustExist) + { + assert(!"No dependent assertions for local var"); + } dep[lclNum] = BitVecOps::MakeEmpty(apTraits); } return dep[lclNum]; @@ -1705,15 +1710,6 @@ void Compiler::optCreateComplementaryAssertion(AssertionIndex assertionIndex, Ge } const AssertionDsc& candidateAssertion = optGetAssertion(assertionIndex); - if ((candidateAssertion.op1.kind == O1K_BOUND_OPER_BND) || (candidateAssertion.op1.kind == O1K_BOUND_LOOP_BND) || - (candidateAssertion.op1.kind == O1K_CONSTANT_LOOP_BND) || - (candidateAssertion.op1.kind == O1K_CONSTANT_LOOP_BND_UN)) - { - AssertionDsc dsc = candidateAssertion; - dsc.ReverseEquality(); - optAddAssertion(dsc); - return; - } if (candidateAssertion.assertionKind == OAK_EQUAL) { @@ -1741,13 +1737,15 @@ void Compiler::optCreateComplementaryAssertion(AssertionIndex assertionIndex, Ge return; } - AssertionIndex index = optCreateAssertion(op1, op2, /*equals*/ false); - optMapComplementary(index, assertionIndex); + AssertionDsc reversed = candidateAssertion.ReverseEquality(); + optMapComplementary(optAddAssertion(reversed), assertionIndex); } else if (candidateAssertion.assertionKind == OAK_NOT_EQUAL) { - AssertionIndex index = optCreateAssertion(op1, op2, /*equals*/ true); - optMapComplementary(index, assertionIndex); + // All OAK_EQUAL assertions are potentially useful + + AssertionDsc reversed = candidateAssertion.ReverseEquality(); + optMapComplementary(optAddAssertion(reversed), assertionIndex); } } @@ -2211,17 +2209,14 @@ AssertionIndex Compiler::optFindComplementary(AssertionIndex assertIndex) return index; } - if (!optLocalAssertionProp) // Seems to be not profitable for Local-AP + for (AssertionIndex index = 1; index <= optAssertionCount; ++index) { - for (AssertionIndex index = 1; index <= optAssertionCount; ++index) + // Make sure assertion kinds are complementary and op1, op2 kinds match. + const AssertionDsc& curAssertion = optGetAssertion(index); + if (curAssertion.Complementary(inputAssertion, !optLocalAssertionProp)) { - // Make sure assertion kinds are complementary and op1, op2 kinds match. - const AssertionDsc& curAssertion = optGetAssertion(index); - if (curAssertion.Complementary(inputAssertion, !optLocalAssertionProp)) - { - optMapComplementary(assertIndex, index); - return index; - } + optMapComplementary(assertIndex, index); + return index; } } return NO_ASSERTION_INDEX; @@ -2244,7 +2239,8 @@ AssertionIndex Compiler::optFindComplementary(AssertionIndex assertIndex) // AssertionIndex Compiler::optAssertionIsSubrange(GenTree* tree, IntegralRange range, ASSERT_VALARG_TP assertions) { - if (!optCanPropSubRange || !optLocalAssertionProp) + assert(optLocalAssertionProp); // Subrange assertions are local only. + if (!optCanPropSubRange) { return NO_ASSERTION_INDEX; } @@ -4564,6 +4560,12 @@ GenTree* Compiler::optAssertionProp_Cast(ASSERT_VALARG_TP assertions, return nullptr; } + if (!optLocalAssertionProp) + { + // optAssertionIsSubrange is only for local assertion prop. + return nullptr; + } + IntegralRange range = IntegralRange::ForCastInput(cast); AssertionIndex index = optAssertionIsSubrange(lcl, range, assertions); if (index != NO_ASSERTION_INDEX) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 2de9a215d0206f..c76d24eb363662 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7898,10 +7898,13 @@ class Compiler return assertionKind == OAK_SUBRANGE && op1.kind == O1K_LCLVAR; } - void ReverseEquality() + AssertionDsc ReverseEquality() const { assert((assertionKind == OAK_EQUAL) || (assertionKind == OAK_NOT_EQUAL)); - assertionKind = assertionKind == OAK_EQUAL ? OAK_NOT_EQUAL : OAK_EQUAL; + + AssertionDsc copy = *this; + copy.assertionKind = assertionKind == OAK_EQUAL ? OAK_NOT_EQUAL : OAK_EQUAL; + return copy; } static bool ComplementaryKind(optAssertionKind kind, optAssertionKind kind2) @@ -8263,11 +8266,11 @@ class Compiler ValueNumToAssertsMap* optValueNumToAsserts; // Assertion prop helpers. - ASSERT_TP& GetAssertionDep(unsigned lclNum); + ASSERT_TP& GetAssertionDep(unsigned lclNum, bool mustExist = false); const AssertionDsc& optGetAssertion(AssertionIndex assertIndex) const; void optAssertionInit(bool isLocalProp); void optAssertionTraitsInit(AssertionIndex assertionCount); - void optAssertionReset(AssertionIndex limit); + void optAssertionReset(); // Assertion prop data flow functions. PhaseStatus optAssertionPropMain(); diff --git a/src/coreclr/jit/compiler.hpp b/src/coreclr/jit/compiler.hpp index 376a441f6e4437..2111dbbf04fe64 100644 --- a/src/coreclr/jit/compiler.hpp +++ b/src/coreclr/jit/compiler.hpp @@ -3828,50 +3828,34 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX * The following resets the assertions table used only during local assertion prop */ -inline void Compiler::optAssertionReset(AssertionIndex limit) +inline void Compiler::optAssertionReset() { + assert(optLocalAssertionProp); assert(optAssertionCount <= optMaxAssertionCount); - - while (optAssertionCount > limit) + while (optAssertionCount > 0) { + // We intentionally don't reset optAssertionDep here to reuse the allocated bitvectors. + // We just remove all elements from them. + // AssertionIndex index = optAssertionCount; const AssertionDsc& curAssertion = optGetAssertion(index); optAssertionCount--; unsigned lclNum = curAssertion.op1.lclNum; assert(lclNum < lvaCount); - BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum), index - 1); + BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum, /*mustExist*/ true), index - 1); // // Find the Copy assertions // - if ((curAssertion.assertionKind == OAK_EQUAL) && (curAssertion.op1.kind == O1K_LCLVAR) && - (curAssertion.op2.kind == O2K_LCLVAR_COPY)) + if ((curAssertion.assertionKind == OAK_EQUAL) && (curAssertion.op2.kind == O2K_LCLVAR_COPY)) { - // - // op2.lclNum no longer depends upon this assertion - // - lclNum = curAssertion.op2.lclNum; - BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum), index - 1); - } - } - while (optAssertionCount < limit) - { - AssertionIndex index = ++optAssertionCount; - const AssertionDsc& curAssertion = optGetAssertion(index); - unsigned lclNum = curAssertion.op1.lclNum; - BitVecOps::AddElemD(apTraits, GetAssertionDep(lclNum), index - 1); + assert(curAssertion.op1.kind == O1K_LCLVAR); - // - // Check for Copy assertions - // - if ((curAssertion.assertionKind == OAK_EQUAL) && (curAssertion.op1.kind == O1K_LCLVAR) && - (curAssertion.op2.kind == O2K_LCLVAR_COPY)) - { // - // op2.lclNum now depends upon this assertion + // op2.lclNum no longer depends upon this assertion // lclNum = curAssertion.op2.lclNum; - BitVecOps::AddElemD(apTraits, GetAssertionDep(lclNum), index - 1); + BitVecOps::RemoveElemD(apTraits, GetAssertionDep(lclNum, /*mustExist*/ true), index - 1); } } } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index f2f6937dd067e3..c7663517d88841 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -13599,7 +13599,7 @@ void Compiler::fgMorphBlock(BasicBlock* block, MorphUnreachableInfo* unreachable { // Each block starts with an empty table, and no available assertions // - optAssertionReset(0); + optAssertionReset(); BitVecOps::ClearD(apTraits, apLocal); BitVecOps::ClearD(apTraits, apLocalPostorder); }