From d27ab3795e5dc8e94ebb46af794d260738fd6792 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Mon, 20 Apr 2026 02:14:18 +0200 Subject: [PATCH 1/5] Add assertions for simd --- src/coreclr/jit/compiler.h | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index e794aee7840842..21caaf8b840196 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -7967,7 +7967,8 @@ class Compiler // "Checked bound" alone doesn't mean anything, // nor it implies that it's never negative. O2K_ZEROOBJ, - O2K_SUBRANGE + O2K_SUBRANGE, + O2K_CONST_VEC }; struct AssertionDsc @@ -8034,6 +8035,7 @@ class Compiler unsigned m_lclNum; double m_dconVal; IntegralRange m_range; + simd_t* m_simdVal; // for O2K_CONST_VEC; arena-allocated (CMK_AssertionProp). struct { ssize_t m_iconVal; @@ -8066,6 +8068,13 @@ class Compiler return m_dconVal; } + const simd_t& GetSimdConstant() const + { + assert(KindIs(O2K_CONST_VEC)); + assert(m_simdVal != nullptr); + return *m_simdVal; + } + ssize_t GetIntConstant() const { assert(KindIs(O2K_CONST_INT)); @@ -8089,7 +8098,7 @@ class Compiler ValueNum GetVN() const { assert(!m_compiler->optLocalAssertionProp); - assert(KindIs(O2K_CONST_INT, O2K_CONST_DOUBLE, O2K_ZEROOBJ)); + assert(KindIs(O2K_CONST_INT, O2K_CONST_DOUBLE, O2K_ZEROOBJ, O2K_CONST_VEC)); assert(m_vn != ValueNumStore::NoVN); return m_vn; } @@ -8417,6 +8426,10 @@ class Compiler // exact match because of positive and negative zero. return (memcmp(&GetOp2().m_dconVal, &that.GetOp2().m_dconVal, sizeof(double)) == 0); + case O2K_CONST_VEC: + // memcmp the full simd_t; GenTreeVecCon zero-inits gtSimdVal before populating, + return (memcmp(GetOp2().m_simdVal, that.GetOp2().m_simdVal, sizeof(simd_t)) == 0); + case O2K_ZEROOBJ: return true; @@ -8459,7 +8472,7 @@ class Compiler static AssertionDsc CreateConstLclVarAssertion(const Compiler* comp, unsigned lclNum, ValueNum vn, - T cns, + const T& cns, ValueNum cnsVN, bool equals, GenTreeFlags iconFlags = GTF_EMPTY, @@ -8508,6 +8521,14 @@ class Compiler assert(iconFlags == GTF_EMPTY); // no flags expected for double constants assert(fldSeq == nullptr); // no fieldSeq expected for double constants } + else if constexpr (std::is_same_v) + { + dsc.m_op2.m_kind = O2K_CONST_VEC; + dsc.m_op2.m_simdVal = new (const_cast(comp), CMK_AssertionProp) simd_t(); + memcpy(dsc.m_op2.m_simdVal, &cns, sizeof(simd_t)); + assert(iconFlags == GTF_EMPTY); // no flags expected for vector constants + assert(fldSeq == nullptr); // no fieldSeq expected for vector constants + } else if constexpr (std::is_same_v) { dsc.m_op2.m_kind = static_cast(cns); From 284554bc9d3a8ddb6d779ee7bcd5d2f15402c683 Mon Sep 17 00:00:00 2001 From: EgorBo Date: Mon, 20 Apr 2026 04:38:17 +0200 Subject: [PATCH 2/5] Add assertions for simd --- src/coreclr/jit/assertionprop.cpp | 115 ++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 71d299ee7c0e75..80ecaf56b9a0a5 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -887,6 +887,10 @@ void Compiler::optPrintAssertion(const AssertionDsc& curAssertion, AssertionInde } break; + case O2K_CONST_VEC: + printf("VecCns"); + break; + case O2K_ZEROOBJ: printf("ZeroObj"); break; @@ -1138,6 +1142,29 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ return optAddAssertion(dsc); } +#if defined(FEATURE_HW_INTRINSICS) + case GT_CNS_VEC: + { + // Only handle SIMD locals where the type matches; LCL_VAR's type tells us the SIMD width. + if (!varTypeIsSIMD(op1) || (op1->TypeGet() != op2->TypeGet())) + { + 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, + op2->AsVecCon()->gtSimdVal, op2VN, equals); + return optAddAssertion(dsc); + } +#endif // FEATURE_HW_INTRINSICS + case GT_CNS_INT: { ValueNum op1VN = optConservativeNormalVN(op1); @@ -1826,6 +1853,69 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) // GenTree* op1 = relop->AsOp()->gtOp1->gtCommaStoreVal(); GenTree* op2 = relop->AsOp()->gtOp2->gtCommaStoreVal(); + if (op1->IsCnsIntOrI() && !op2->IsCnsIntOrI()) + { + std::swap(op1, op2); + } + +#if defined(FEATURE_HW_INTRINSICS) + if (op1->OperIsHWIntrinsic() && (op2->IsIntegralConst(0) || op2->IsIntegralConst(1))) + { + // We have ==/!= 0/1, we need to normalize it to ==/!= 1 + if (op2->IsIntegralConst(0)) + { + equals = !equals; + } + + GenTreeHWIntrinsic* hwi = op1->AsHWIntrinsic(); + switch (hwi->GetHWIntrinsicId()) + { +#if defined(TARGET_XARCH) + case NI_Vector128_op_Equality: + case NI_Vector256_op_Equality: + case NI_Vector512_op_Equality: +#elif defined(TARGET_ARM64) + case NI_Vector64_op_Equality: + case NI_Vector128_op_Equality: +#endif + break; +#if defined(TARGET_XARCH) + case NI_Vector128_op_Inequality: + case NI_Vector256_op_Inequality: + case NI_Vector512_op_Inequality: +#elif defined(TARGET_ARM64) + case NI_Vector64_op_Inequality: + case NI_Vector128_op_Inequality: +#endif + equals = !equals; + break; + + default: + return NO_ASSERTION_INDEX; + } + + // SIMD floating-point equality is not bitwise equality (+0 == -0, NaN != NaN), + // Only enable for integral SIMD base types. + if (varTypeIsIntegral(hwi->GetSimdBaseType()) && (hwi->GetOperandCount() == 2)) + { + op1 = hwi->Op(1); + op2 = hwi->Op(2); + if (op1->OperIs(GT_CNS_VEC)) + { + std::swap(op1, op2); + } + + if (!op2->OperIs(GT_CNS_VEC)) + { + return NO_ASSERTION_INDEX; + } + } + else + { + return NO_ASSERTION_INDEX; + } + } +#endif // FEATURE_HW_INTRINSICS // Avoid creating local assertions for float types. // @@ -3174,6 +3264,23 @@ GenTree* Compiler::optConstantAssertionProp(const AssertionDsc& curAssertion, newTree->BashToConst(curAssertion.GetOp2().GetDoubleConstant(), tree->TypeGet()); break; +#if defined(FEATURE_HW_INTRINSICS) + case O2K_CONST_VEC: + { + // The assertion was created from a LCL_VAR == CNS_VEC where types matched. Verify type still matches. + if (!varTypeIsSIMD(tree) || !tree->TypeIs(lvaGetDesc(lclNum)->TypeGet())) + { + return nullptr; + } + + // We can't bash a LCL_VAR into a GenTreeVecCon (different node size), so allocate a fresh node. + GenTreeVecCon* vecCon = gtNewVconNode(tree->TypeGet()); + memcpy(&vecCon->gtSimdVal, &curAssertion.GetOp2().GetSimdConstant(), genTypeSize(tree->TypeGet())); + newTree = vecCon; + break; + } +#endif // FEATURE_HW_INTRINSICS + case O2K_CONST_INT: // Don't propagate non-nulll non-static handles if we need to report relocs. @@ -3493,9 +3600,9 @@ GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeL return nullptr; } - // There are no constant assertions for structs in global propagation. + // There are no constant assertions for structs in global propagation (except SIMD vector constants). // - if ((!optLocalAssertionProp && varTypeIsStruct(tree)) || !optCanPropLclVar) + if ((!optLocalAssertionProp && varTypeIsStruct(tree) && !varTypeIsSIMD(tree)) || !optCanPropLclVar) { return nullptr; } @@ -3552,9 +3659,9 @@ GenTree* Compiler::optAssertionProp_LclVar(ASSERT_VALARG_TP assertions, GenTreeL continue; } - // There are no constant assertions for structs. + // There are no constant assertions for structs (except SIMD vector constants). // - if (varTypeIsStruct(tree)) + if (varTypeIsStruct(tree) && !varTypeIsSIMD(tree)) { continue; } From f513c7cb73d85253e0614e7952b3632d416c007f Mon Sep 17 00:00:00 2001 From: EgorBo Date: Mon, 20 Apr 2026 05:00:17 +0200 Subject: [PATCH 3/5] cleanup --- src/coreclr/jit/compiler.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 21caaf8b840196..f6a1a953298717 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8035,7 +8035,7 @@ class Compiler unsigned m_lclNum; double m_dconVal; IntegralRange m_range; - simd_t* m_simdVal; // for O2K_CONST_VEC; arena-allocated (CMK_AssertionProp). + simd_t m_simdVal; // for O2K_CONST_VEC; arena-allocated (CMK_AssertionProp). struct { ssize_t m_iconVal; @@ -8071,8 +8071,7 @@ class Compiler const simd_t& GetSimdConstant() const { assert(KindIs(O2K_CONST_VEC)); - assert(m_simdVal != nullptr); - return *m_simdVal; + return m_simdVal; } ssize_t GetIntConstant() const @@ -8428,7 +8427,7 @@ class Compiler case O2K_CONST_VEC: // memcmp the full simd_t; GenTreeVecCon zero-inits gtSimdVal before populating, - return (memcmp(GetOp2().m_simdVal, that.GetOp2().m_simdVal, sizeof(simd_t)) == 0); + return (memcmp(&GetOp2().m_simdVal, &that.GetOp2().m_simdVal, sizeof(simd_t)) == 0); case O2K_ZEROOBJ: return true; @@ -8524,8 +8523,8 @@ class Compiler else if constexpr (std::is_same_v) { dsc.m_op2.m_kind = O2K_CONST_VEC; - dsc.m_op2.m_simdVal = new (const_cast(comp), CMK_AssertionProp) simd_t(); - memcpy(dsc.m_op2.m_simdVal, &cns, sizeof(simd_t)); + dsc.m_op2.m_simdVal = {}; + memcpy(&dsc.m_op2.m_simdVal, &cns, sizeof(simd_t)); assert(iconFlags == GTF_EMPTY); // no flags expected for vector constants assert(fldSeq == nullptr); // no fieldSeq expected for vector constants } From 6c511dd4b9be3b4e348eb476c3af5341ecbcc2b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:12:01 +0000 Subject: [PATCH 4/5] Limit SIMD assertion payload to simd16_t for const vec assertions Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e8f6f2ee-359c-4b32-b822-877ec029ddc6 Co-authored-by: EgorBo <523221+EgorBo@users.noreply.github.com> --- src/coreclr/jit/assertionprop.cpp | 21 +++++++++++++++------ src/coreclr/jit/compiler.h | 12 ++++++------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 80ecaf56b9a0a5..6facd7cdad87c7 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1145,8 +1145,8 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ #if defined(FEATURE_HW_INTRINSICS) case GT_CNS_VEC: { - // Only handle SIMD locals where the type matches; LCL_VAR's type tells us the SIMD width. - if (!varTypeIsSIMD(op1) || (op1->TypeGet() != op2->TypeGet())) + // For now, only support SIMD constants up to 16 bytes (SIMD8/12/16). + if (!op1->TypeIs(TYP_SIMD8, TYP_SIMD12, TYP_SIMD16) || (op1->TypeGet() != op2->TypeGet())) { return NO_ASSERTION_INDEX; } @@ -1159,8 +1159,11 @@ AssertionIndex Compiler::optCreateAssertion(GenTree* op1, GenTree* op2, bool equ return NO_ASSERTION_INDEX; } - AssertionDsc dsc = AssertionDsc::CreateConstLclVarAssertion(this, lclNum, op1VN, - op2->AsVecCon()->gtSimdVal, op2VN, equals); + simd16_t simdVal = {}; + memcpy(&simdVal, &op2->AsVecCon()->gtSimdVal, genTypeSize(op2->TypeGet())); + + AssertionDsc dsc = + AssertionDsc::CreateConstLclVarAssertion(this, lclNum, op1VN, simdVal, op2VN, equals); return optAddAssertion(dsc); } #endif // FEATURE_HW_INTRINSICS @@ -1909,6 +1912,11 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) { return NO_ASSERTION_INDEX; } + + if (!op1->TypeIs(TYP_SIMD8, TYP_SIMD12, TYP_SIMD16) || (op1->TypeGet() != op2->TypeGet())) + { + return NO_ASSERTION_INDEX; + } } else { @@ -3267,8 +3275,9 @@ GenTree* Compiler::optConstantAssertionProp(const AssertionDsc& curAssertion, #if defined(FEATURE_HW_INTRINSICS) case O2K_CONST_VEC: { - // The assertion was created from a LCL_VAR == CNS_VEC where types matched. Verify type still matches. - if (!varTypeIsSIMD(tree) || !tree->TypeIs(lvaGetDesc(lclNum)->TypeGet())) + // The assertion was created from a LCL_VAR == CNS_VEC where types matched. + // For now, only support SIMD constants up to 16 bytes (SIMD8/12/16). + if (!tree->TypeIs(TYP_SIMD8, TYP_SIMD12, TYP_SIMD16) || !tree->TypeIs(lvaGetDesc(lclNum)->TypeGet())) { return nullptr; } diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index f6a1a953298717..3cc78c894f8c47 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8035,7 +8035,7 @@ class Compiler unsigned m_lclNum; double m_dconVal; IntegralRange m_range; - simd_t m_simdVal; // for O2K_CONST_VEC; arena-allocated (CMK_AssertionProp). + simd16_t m_simdVal; // for O2K_CONST_VEC (TYP_SIMD8/12/16 only). TODO-CQ: support wider SIMD via heap allocation. struct { ssize_t m_iconVal; @@ -8068,7 +8068,7 @@ class Compiler return m_dconVal; } - const simd_t& GetSimdConstant() const + const simd16_t& GetSimdConstant() const { assert(KindIs(O2K_CONST_VEC)); return m_simdVal; @@ -8426,8 +8426,8 @@ class Compiler return (memcmp(&GetOp2().m_dconVal, &that.GetOp2().m_dconVal, sizeof(double)) == 0); case O2K_CONST_VEC: - // memcmp the full simd_t; GenTreeVecCon zero-inits gtSimdVal before populating, - return (memcmp(&GetOp2().m_simdVal, &that.GetOp2().m_simdVal, sizeof(simd_t)) == 0); + // memcmp the full stored payload; GenTreeVecCon zero-inits gtSimdVal before populating. + return (memcmp(&GetOp2().m_simdVal, &that.GetOp2().m_simdVal, sizeof(simd16_t)) == 0); case O2K_ZEROOBJ: return true; @@ -8520,11 +8520,11 @@ class Compiler assert(iconFlags == GTF_EMPTY); // no flags expected for double constants assert(fldSeq == nullptr); // no fieldSeq expected for double constants } - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) { dsc.m_op2.m_kind = O2K_CONST_VEC; dsc.m_op2.m_simdVal = {}; - memcpy(&dsc.m_op2.m_simdVal, &cns, sizeof(simd_t)); + memcpy(&dsc.m_op2.m_simdVal, &cns, sizeof(simd16_t)); assert(iconFlags == GTF_EMPTY); // no flags expected for vector constants assert(fldSeq == nullptr); // no fieldSeq expected for vector constants } From c2f106694a68183891a29f2ad8d9d560c6cb041c Mon Sep 17 00:00:00 2001 From: EgorBo Date: Thu, 23 Apr 2026 00:58:27 +0200 Subject: [PATCH 5/5] feedback --- src/coreclr/jit/assertionprop.cpp | 23 +++++------------------ src/coreclr/jit/compiler.h | 3 ++- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 6facd7cdad87c7..e042d5f09bd8f5 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1856,10 +1856,6 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) // GenTree* op1 = relop->AsOp()->gtOp1->gtCommaStoreVal(); GenTree* op2 = relop->AsOp()->gtOp2->gtCommaStoreVal(); - if (op1->IsCnsIntOrI() && !op2->IsCnsIntOrI()) - { - std::swap(op1, op2); - } #if defined(FEATURE_HW_INTRINSICS) if (op1->OperIsHWIntrinsic() && (op2->IsIntegralConst(0) || op2->IsIntegralConst(1))) @@ -1875,8 +1871,6 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) { #if defined(TARGET_XARCH) case NI_Vector128_op_Equality: - case NI_Vector256_op_Equality: - case NI_Vector512_op_Equality: #elif defined(TARGET_ARM64) case NI_Vector64_op_Equality: case NI_Vector128_op_Equality: @@ -1884,8 +1878,6 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) break; #if defined(TARGET_XARCH) case NI_Vector128_op_Inequality: - case NI_Vector256_op_Inequality: - case NI_Vector512_op_Inequality: #elif defined(TARGET_ARM64) case NI_Vector64_op_Inequality: case NI_Vector128_op_Inequality: @@ -1899,24 +1891,19 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree) // SIMD floating-point equality is not bitwise equality (+0 == -0, NaN != NaN), // Only enable for integral SIMD base types. - if (varTypeIsIntegral(hwi->GetSimdBaseType()) && (hwi->GetOperandCount() == 2)) + if (varTypeIsIntegral(hwi->GetSimdBaseType())) { + assert(hwi->GetOperandCount() == 2); op1 = hwi->Op(1); op2 = hwi->Op(2); - if (op1->OperIs(GT_CNS_VEC)) - { - std::swap(op1, op2); - } - if (!op2->OperIs(GT_CNS_VEC)) + if (!op2->IsCnsVec()) { return NO_ASSERTION_INDEX; } - if (!op1->TypeIs(TYP_SIMD8, TYP_SIMD12, TYP_SIMD16) || (op1->TypeGet() != op2->TypeGet())) - { - return NO_ASSERTION_INDEX; - } + assert(op1->TypeIs(TYP_SIMD8, TYP_SIMD12, TYP_SIMD16)); + assert(op1->TypeIs(op2->TypeGet())); } else { diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 3cc78c894f8c47..4a1dde13664213 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -8035,7 +8035,8 @@ class Compiler unsigned m_lclNum; double m_dconVal; IntegralRange m_range; - simd16_t m_simdVal; // for O2K_CONST_VEC (TYP_SIMD8/12/16 only). TODO-CQ: support wider SIMD via heap allocation. + simd16_t m_simdVal; // for O2K_CONST_VEC (TYP_SIMD8/12/16 only). TODO-CQ: support wider SIMD via heap + // allocation. struct { ssize_t m_iconVal;