From b815b05ff245075eda6972cbf4d9dafe8aacde5e Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Tue, 11 Aug 2026 08:07:10 +0000 Subject: [PATCH 1/3] arm64: Optimize Vector helper APIs https://github.com/dotnet/runtime/pull/121981#discussion_r2564553235 Import Vector64/128 helper APIs through Arm64 compare masks so Count, IndexOf, LastIndexOf, and related Any/All/None helpers can benefit from existing ExtractMostSignificantBits lowering. Preserve Scalar.Equals NaN semantics for floating-point value helpers and keep unsupported 64-bit mask encodings on fallback paths. --- src/coreclr/jit/hwintrinsic.cpp | 424 ++++++++++++++++++ src/coreclr/jit/hwintrinsiclist.h | 12 + .../InstructionCombining/VectorHelperApis.cs | 290 ++++++++++++ .../VectorHelperApis.csproj | 16 + 4 files changed, 742 insertions(+) create mode 100644 src/tests/JIT/opt/InstructionCombining/VectorHelperApis.cs create mode 100644 src/tests/JIT/opt/InstructionCombining/VectorHelperApis.csproj diff --git a/src/coreclr/jit/hwintrinsic.cpp b/src/coreclr/jit/hwintrinsic.cpp index a1058fbffb0ccf..cb3e0b80078aaa 100644 --- a/src/coreclr/jit/hwintrinsic.cpp +++ b/src/coreclr/jit/hwintrinsic.cpp @@ -3030,8 +3030,432 @@ GenTree* Compiler::impXplatIntrinsic(NamedIntrinsic intrinsic, bool leftUpper = false; bool rightUpper = false; + auto vectorFirstArgMatchesSimdSize = [this, sig, simdSize]() { + // Vector2/3/4 and the fixed-size Vector64/128 helpers share some method names. Make sure this importer + // only handles the generic Vector64/Vector128 methods whose first argument has the expected SIMD size. + if (sig->sigInst.methInstCount != 1) + { + return false; + } + + CORINFO_CLASS_HANDLE argClass = NO_CLASS_HANDLE; + CORINFO_ARG_LIST_HANDLE arg = sig->args; + var_types argType = JITtype2varType(strip(info.compCompHnd->getArgType(sig, arg, &argClass))); + + if (argType == TYP_STRUCT) + { + return info.compCompHnd->getClassSize(argClass) == simdSize; + } + + return varTypeIsSIMD(argType) && (genTypeSize(argType) == simdSize); + }; + + auto vectorValueEqualsMask = [this, simdSize](GenTree* vector, GenTree* value, var_types simdBaseType) -> GenTree* { + var_types simdType = getSIMDTypeForSize(simdSize); + GenTree* other = gtNewSimdCreateBroadcastNode(simdType, value, simdBaseType, simdSize); + NamedIntrinsic compareEqual = GenTreeHWIntrinsic::GetHWIntrinsicIdForCmpOp(this, GT_EQ, simdType, vector, other, + simdBaseType, simdSize, false); + return gtNewSimdHWIntrinsicNode(simdType, vector, other, compareEqual, simdBaseType, simdSize); + }; + + auto vectorAllBitsSetMask = [this, simdSize](GenTree* vector, var_types simdBaseType, + var_types* maskBaseType) -> GenTree* { + var_types simdType = getSIMDTypeForSize(simdSize); + *maskBaseType = getUnsignedSimdBaseType(simdBaseType); + GenTree* allBitsSet = gtNewAllBitsSetConNode(simdType); + NamedIntrinsic compareEqual = + GenTreeHWIntrinsic::GetHWIntrinsicIdForCmpOp(this, GT_EQ, simdType, vector, allBitsSet, *maskBaseType, + simdSize, false); + return gtNewSimdHWIntrinsicNode(simdType, vector, allBitsSet, compareEqual, *maskBaseType, simdSize); + }; + + auto vectorExtractMostSignificantBits = [this, simdSize](GenTree* mask, var_types maskBaseType) -> GenTree* { +#if defined(TARGET_XARCH) + NamedIntrinsic extractIntrinsic; + + switch (maskBaseType) + { + case TYP_BYTE: + case TYP_UBYTE: + extractIntrinsic = (simdSize == 32) ? NI_AVX2_MoveMask : NI_X86Base_MoveMask; + break; + + case TYP_SHORT: + case TYP_USHORT: + return gtNewSimdHWIntrinsicNode(TYP_INT, mask, NI_Vector_ExtractMostSignificantBits, maskBaseType, + simdSize); + + case TYP_INT: + case TYP_UINT: + case TYP_FLOAT: + maskBaseType = TYP_FLOAT; + extractIntrinsic = (simdSize == 32) ? NI_AVX_MoveMask : NI_X86Base_MoveMask; + break; + + default: + unreached(); + } + + return gtNewSimdHWIntrinsicNode(TYP_INT, mask, extractIntrinsic, maskBaseType, simdSize); +#else + return gtNewSimdHWIntrinsicNode(TYP_INT, mask, NI_Vector_ExtractMostSignificantBits, maskBaseType, simdSize); +#endif + }; + + auto vectorCountFromMask = [this, &vectorExtractMostSignificantBits](GenTree* mask, + var_types maskBaseType) -> GenTree* { + // Count/IndexOf/LastIndexOf are expressed in terms of a vector compare mask followed by + // ExtractMostSignificantBits. Xarch imports this as MoveMask, while the existing Arm64 rationalization + // recognizes the full pattern and chooses the best mask-count/index sequence. + GenTree* extract = vectorExtractMostSignificantBits(mask, maskBaseType); +#if defined(TARGET_XARCH) + return gtNewScalarHWIntrinsicNode(TYP_INT, extract, NI_X86Base_PopCount); +#else + return new (this, GT_INTRINSIC) + GenTreeIntrinsic(TYP_INT, extract, NI_PRIMITIVE_PopCount, nullptr R2RARG(CORINFO_CONST_LOOKUP{IAT_VALUE})); +#endif + }; + + auto vectorIndexOfFromMask = [this, &vectorExtractMostSignificantBits](GenTree* mask, + var_types maskBaseType) -> GenTree* { + GenTree* extract = vectorExtractMostSignificantBits(mask, maskBaseType); +#if defined(TARGET_XARCH) + GenTree* tzcnt = gtNewScalarHWIntrinsicNode(TYP_INT, extract, NI_AVX2_TrailingZeroCount); +#else + GenTree* tzcnt = new (this, GT_INTRINSIC) GenTreeIntrinsic(TYP_INT, extract, NI_PRIMITIVE_TrailingZeroCount, + nullptr R2RARG(CORINFO_CONST_LOOKUP{IAT_VALUE})); +#endif + + // TrailingZeroCount returns 32 for a zero mask. The managed helper returns -1 when no element matched. + unsigned tzcntTmp = lvaGrabTemp(true DEBUGARG("Vector IndexOf result")); + impStoreToTemp(tzcntTmp, tzcnt, CHECK_SPILL_ALL); + + GenTree* cond = gtNewOperNode(GT_EQ, TYP_INT, gtNewLclvNode(tzcntTmp, TYP_INT), gtNewIconNode(32)); + GenTree* whenNoMatch = gtNewIconNode(-1); + GenTree* whenMatched = gtNewLclvNode(tzcntTmp, TYP_INT); + GenTree* result = gtNewQmarkNode(TYP_INT, cond, gtNewColonNode(TYP_INT, whenNoMatch, whenMatched)); + + unsigned resultTmp = lvaGrabTemp(true DEBUGARG("Vector IndexOf qmark")); + impStoreToTemp(resultTmp, result, CHECK_SPILL_NONE); + return gtNewLclvNode(resultTmp, TYP_INT); + }; + + auto vectorLastIndexOfFromMask = [this, &vectorExtractMostSignificantBits](GenTree* mask, + var_types maskBaseType) -> GenTree* { + GenTree* extract = vectorExtractMostSignificantBits(mask, maskBaseType); +#if defined(TARGET_ARM64) + GenTree* lzcnt = gtNewScalarHWIntrinsicNode(TYP_INT, extract, NI_ArmBase_LeadingZeroCount); +#elif defined(TARGET_XARCH) + GenTree* lzcnt = gtNewScalarHWIntrinsicNode(TYP_INT, extract, NI_AVX2_LeadingZeroCount); +#else + GenTree* lzcnt = new (this, GT_INTRINSIC) GenTreeIntrinsic(TYP_INT, extract, NI_PRIMITIVE_LeadingZeroCount, + nullptr R2RARG(CORINFO_CONST_LOOKUP{IAT_VALUE})); +#endif + return gtNewOperNode(GT_SUB, TYP_INT, gtNewIconNode(31), lzcnt); + }; + switch (intrinsic) { + case NI_Vector_All: + { + assert(sig->numArgs == 2); + + if (!vectorFirstArgMatchesSimdSize()) + { + break; + } + + op2 = impPopStack().val; + op1 = impSIMDPopStack(); + + GenTree* other = gtNewSimdCreateBroadcastNode(getSIMDTypeForSize(simdSize), op2, simdBaseType, simdSize); + retNode = gtNewSimdCmpOpAllNode(GT_EQ, retType, op1, other, simdBaseType, simdSize); + break; + } + + case NI_Vector_AllWhereAllBitsSet: + { + assert(sig->numArgs == 1); + + if (!vectorFirstArgMatchesSimdSize()) + { + break; + } + + var_types maskBaseType = getUnsignedSimdBaseType(simdBaseType); + op1 = impSIMDPopStack(); + retNode = gtNewSimdCmpOpAllNode(GT_EQ, retType, op1, gtNewAllBitsSetConNode(getSIMDTypeForSize(simdSize)), + maskBaseType, simdSize); + break; + } + + case NI_Vector_Any: + { + assert(sig->numArgs == 2); + + if (!vectorFirstArgMatchesSimdSize()) + { + break; + } + + op2 = impPopStack().val; + op1 = impSIMDPopStack(); + + retNode = gtNewSimdCmpOpAnyNode(GT_EQ, retType, op1, + gtNewSimdCreateBroadcastNode(getSIMDTypeForSize(simdSize), op2, + simdBaseType, simdSize), + simdBaseType, simdSize); + break; + } + + case NI_Vector_AnyWhereAllBitsSet: + { + assert(sig->numArgs == 1); + + if (!vectorFirstArgMatchesSimdSize()) + { + break; + } + + op1 = impSIMDPopStack(); + retNode = gtNewSimdCmpOpAnyNode(GT_EQ, retType, op1, gtNewAllBitsSetConNode(getSIMDTypeForSize(simdSize)), + getUnsignedSimdBaseType(simdBaseType), simdSize); + break; + } + + case NI_Vector_Count: + { + assert(sig->numArgs == 2); + + if (!vectorFirstArgMatchesSimdSize()) + { + break; + } + + // Floating-point equality has different semantics from the integer mask comparisons used below. + // Leave those cases on the managed fallback path for now. + if (varTypeIsFloating(simdBaseType)) + { + break; + } + + // The mask is represented as a 32-bit value, so only import element sizes that fit that encoding. + if (genTypeSize(simdBaseType) > 4) + { + // ExtractMostSignificantBits encodes the mask in a 32-bit integer, so leave larger element + // sizes on the managed fallback path. + break; + } + + op2 = impPopStack().val; + op1 = impSIMDPopStack(); + + GenTree* mask = vectorValueEqualsMask(op1, op2, simdBaseType); + retNode = vectorCountFromMask(mask, simdBaseType); + break; + } + + case NI_Vector_CountWhereAllBitsSet: + { + assert(sig->numArgs == 1); + + if (!vectorFirstArgMatchesSimdSize()) + { + break; + } + + if (genTypeSize(getUnsignedSimdBaseType(simdBaseType)) > 4) + { + // ExtractMostSignificantBits encodes the mask in a 32-bit integer, so leave larger element + // sizes on the managed fallback path. + break; + } + + var_types maskBaseType; + op1 = impSIMDPopStack(); + GenTree* mask = vectorAllBitsSetMask(op1, simdBaseType, &maskBaseType); + retNode = vectorCountFromMask(mask, maskBaseType); + break; + } + + case NI_Vector_IndexOf: + { + assert(sig->numArgs == 2); + + if (!vectorFirstArgMatchesSimdSize()) + { + break; + } + +#if defined(TARGET_XARCH) + if (!compOpportunisticallyDependsOn(InstructionSet_AVX2)) + { + break; + } +#endif + + // Floating-point equality has different semantics from the integer mask comparisons used below. + // Leave those cases on the managed fallback path for now. + if (varTypeIsFloating(simdBaseType)) + { + break; + } + + // The mask is represented as a 32-bit value, so only import element sizes that fit that encoding. + if (genTypeSize(simdBaseType) > 4) + { + // ExtractMostSignificantBits encodes the mask in a 32-bit integer, so leave larger element + // sizes on the managed fallback path. + break; + } + + op2 = impPopStack().val; + op1 = impSIMDPopStack(); + + GenTree* mask = vectorValueEqualsMask(op1, op2, simdBaseType); + retNode = vectorIndexOfFromMask(mask, simdBaseType); + break; + } + + case NI_Vector_IndexOfWhereAllBitsSet: + { + assert(sig->numArgs == 1); + + if (!vectorFirstArgMatchesSimdSize()) + { + break; + } + +#if defined(TARGET_XARCH) + if (!compOpportunisticallyDependsOn(InstructionSet_AVX2)) + { + break; + } +#endif + + if (genTypeSize(getUnsignedSimdBaseType(simdBaseType)) > 4) + { + // ExtractMostSignificantBits encodes the mask in a 32-bit integer, so leave larger element + // sizes on the managed fallback path. + break; + } + + var_types maskBaseType; + op1 = impSIMDPopStack(); + GenTree* mask = vectorAllBitsSetMask(op1, simdBaseType, &maskBaseType); + retNode = vectorIndexOfFromMask(mask, maskBaseType); + break; + } + + case NI_Vector_LastIndexOf: + { + assert(sig->numArgs == 2); + + if (!vectorFirstArgMatchesSimdSize()) + { + break; + } + +#if defined(TARGET_XARCH) + if (!compOpportunisticallyDependsOn(InstructionSet_AVX2)) + { + break; + } +#endif + + // Floating-point equality has different semantics from the integer mask comparisons used below. + // Leave those cases on the managed fallback path for now. + if (varTypeIsFloating(simdBaseType)) + { + break; + } + + // The mask is represented as a 32-bit value, so only import element sizes that fit that encoding. + if (genTypeSize(simdBaseType) > 4) + { + // ExtractMostSignificantBits encodes the mask in a 32-bit integer, so leave larger element + // sizes on the managed fallback path. + break; + } + + op2 = impPopStack().val; + op1 = impSIMDPopStack(); + + GenTree* mask = vectorValueEqualsMask(op1, op2, simdBaseType); + retNode = vectorLastIndexOfFromMask(mask, simdBaseType); + break; + } + + case NI_Vector_LastIndexOfWhereAllBitsSet: + { + assert(sig->numArgs == 1); + + if (!vectorFirstArgMatchesSimdSize()) + { + break; + } + +#if defined(TARGET_XARCH) + if (!compOpportunisticallyDependsOn(InstructionSet_AVX2)) + { + break; + } +#endif + + if (genTypeSize(getUnsignedSimdBaseType(simdBaseType)) > 4) + { + // ExtractMostSignificantBits encodes the mask in a 32-bit integer, so leave larger element + // sizes on the managed fallback path. + break; + } + + var_types maskBaseType; + op1 = impSIMDPopStack(); + GenTree* mask = vectorAllBitsSetMask(op1, simdBaseType, &maskBaseType); + retNode = vectorLastIndexOfFromMask(mask, maskBaseType); + break; + } + + case NI_Vector_None: + { + assert(sig->numArgs == 2); + + if (!vectorFirstArgMatchesSimdSize()) + { + break; + } + + op2 = impPopStack().val; + op1 = impSIMDPopStack(); + + // Import None(value) as !Any(value) so it benefits from the same compare-mask lowering as Any. + GenTree* any = gtNewSimdCmpOpAnyNode(GT_EQ, retType, op1, + gtNewSimdCreateBroadcastNode(getSIMDTypeForSize(simdSize), op2, + simdBaseType, simdSize), + simdBaseType, simdSize); + retNode = gtNewOperNode(GT_EQ, retType, any, gtNewZeroConNode(retType)); + break; + } + + case NI_Vector_NoneWhereAllBitsSet: + { + assert(sig->numArgs == 1); + + if (!vectorFirstArgMatchesSimdSize()) + { + break; + } + + op1 = impSIMDPopStack(); + + // Import NoneWhereAllBitsSet as !AnyWhereAllBitsSet for the same reason. + GenTree* any = + gtNewSimdCmpOpAnyNode(GT_EQ, retType, op1, gtNewAllBitsSetConNode(getSIMDTypeForSize(simdSize)), + getUnsignedSimdBaseType(simdBaseType), simdSize); + retNode = gtNewOperNode(GT_EQ, retType, any, gtNewZeroConNode(retType)); + break; + } + case NI_Vector_Abs: { assert(sig->numArgs == 1); diff --git a/src/coreclr/jit/hwintrinsiclist.h b/src/coreclr/jit/hwintrinsiclist.h index d838afa24e8f74..ca2eec0bb92865 100644 --- a/src/coreclr/jit/hwintrinsiclist.h +++ b/src/coreclr/jit/hwintrinsiclist.h @@ -35,7 +35,11 @@ #define FIRST_NI_Vector NI_Vector_Abs HARDWARE_INTRINSIC(Vector, Abs, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector, AddSaturate, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector, All, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(Vector, AllWhereAllBitsSet, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(Vector, AndNot, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector, Any, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(Vector, AnyWhereAllBitsSet, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(Vector, As, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_BaseTypeFromFirstArg|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector, AsByte, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_BaseTypeFromFirstArg|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector, AsDouble, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_BaseTypeFromFirstArg|HW_Flag_AvxOnlyCompatible) @@ -95,6 +99,8 @@ HARDWARE_INTRINSIC(Vector, ConvertToUInt32, HARDWARE_INTRINSIC(Vector, ConvertToUInt32Native, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(Vector, ConvertToUInt64, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(Vector, ConvertToUInt64Native, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(Vector, Count, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(Vector, CountWhereAllBitsSet, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(Vector, Create, -1, -1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector, CreateAlternatingSequence, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector, CreateGeometricSequence, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) @@ -142,6 +148,8 @@ HARDWARE_INTRINSIC(Vector, GreaterThanOrEqual, HARDWARE_INTRINSIC(Vector, GreaterThanOrEqualAll, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(Vector, GreaterThanOrEqualAny, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(Vector, IndexOf, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(Vector, IndexOfWhereAllBitsSet, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(Vector, IsEvenInteger, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId) HARDWARE_INTRINSIC(Vector, IsFinite, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector, IsInfinity, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_AvxOnlyCompatible) @@ -155,6 +163,8 @@ HARDWARE_INTRINSIC(Vector, IsPositive, HARDWARE_INTRINSIC(Vector, IsPositiveInfinity, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector, IsSubnormal, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector, IsZero, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector, LastIndexOf, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(Vector, LastIndexOfWhereAllBitsSet, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(Vector, LessThan, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_BaseTypeFromFirstArg|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector, LessThanAll, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(Vector, LessThanAny, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_BaseTypeFromFirstArg) @@ -183,6 +193,8 @@ HARDWARE_INTRINSIC(Vector, NarrowWithSaturation, HARDWARE_INTRINSIC(Vector, NarrowWithSaturation, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_BaseTypeFromFirstArg|HW_Flag_AvxOnlyCompatible) #endif +HARDWARE_INTRINSIC(Vector, None, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(Vector, NoneWhereAllBitsSet, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(Vector, Reverse, -1, 1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId) HARDWARE_INTRINSIC(Vector, Round, -1, -1, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector, ShiftLeft, -1, 2, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, -1, -1, HW_Category_Helper, HW_Flag_InvalidNodeId) diff --git a/src/tests/JIT/opt/InstructionCombining/VectorHelperApis.cs b/src/tests/JIT/opt/InstructionCombining/VectorHelperApis.cs new file mode 100644 index 00000000000000..887b43a155007a --- /dev/null +++ b/src/tests/JIT/opt/InstructionCombining/VectorHelperApis.cs @@ -0,0 +1,290 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using Xunit; + +namespace TestVectorHelperApis +{ + public class Program + { + private static Vector128 s_evaluationOrderVector; + + [Fact] + public static int TestEntryPoint() + { + bool fail = false; + + Vector128 bytes128 = Vector128.Create((byte)1, (byte)2, (byte)3, (byte)2, (byte)4, (byte)2, (byte)5, (byte)2, + (byte)6, (byte)2, (byte)7, (byte)2, (byte)8, (byte)2, (byte)9, (byte)2); + Vector64 bytes64 = Vector64.Create((byte)1, (byte)2, (byte)3, (byte)2, (byte)4, (byte)2, (byte)5, (byte)2); + + fail |= CountByte128(bytes128, 2) != 8; + fail |= IndexOfByte128(bytes128, 2) != 1; + fail |= LastIndexOfByte128(bytes128, 2) != 15; + fail |= NoneByte128(bytes128, 42) != true; + fail |= AnyByte128(bytes128, 2) != true; + fail |= AllByte128(Vector128.Create((byte)2), 2) != true; + + fail |= CountByte64(bytes64, 2) != 4; + fail |= IndexOfByte64(bytes64, 2) != 1; + fail |= LastIndexOfByte64(bytes64, 2) != 7; + fail |= NoneByte64(bytes64, 42) != true; + + Vector512 bytes512 = Vector512.Zero.WithElement(63, (byte)2); + + fail |= CountByte512(bytes512, 2) != 1; + fail |= IndexOfByte512(bytes512, 2) != 63; + fail |= LastIndexOfByte512(bytes512, 2) != 63; + + Vector128 allBits128 = Vector128.Create(-1, 0, -1, 7); + Vector64 allBits64 = Vector64.Create(-1, 0); + + fail |= CountAllBitsSetInt128(allBits128) != 2; + fail |= IndexOfAllBitsSetInt128(allBits128) != 0; + fail |= LastIndexOfAllBitsSetInt128(allBits128) != 2; + fail |= AnyAllBitsSetInt128(allBits128) != true; + fail |= AllAllBitsSetInt128(Vector128.AllBitsSet) != true; + fail |= NoneAllBitsSetInt128(Vector128.Zero) != true; + + fail |= CountAllBitsSetInt64(allBits64) != 1; + fail |= IndexOfAllBitsSetInt64(allBits64) != 0; + fail |= LastIndexOfAllBitsSetInt64(allBits64) != 0; + fail |= NoneAllBitsSetInt64(Vector64.Zero) != true; + + fail |= IndexOfEvaluationOrder() != 10; + fail |= IndexOfWhereAllBitsSetEvaluationOrder() != 10; + + Vector128 floatBits = Vector128.Create(BitConverter.Int32BitsToSingle(-1), 0.0f, BitConverter.Int32BitsToSingle(-1), 1.0f); + fail |= CountAllBitsSetFloat128(floatBits) != 2; + fail |= IndexOfAllBitsSetFloat128(floatBits) != 0; + fail |= NoneAllBitsSetFloat128(Vector128.Zero) != true; + + Vector128 floatNaN128 = Vector128.Create(float.NaN); + fail |= AllFloatNaN128(floatNaN128, float.NaN) != false; + fail |= AnyFloatNaN128(floatNaN128, float.NaN) != false; + fail |= NoneFloatNaN128(floatNaN128, float.NaN) != true; + + Vector64 floatNaN64 = Vector64.Create(float.NaN); + fail |= AllFloatNaN64(floatNaN64, float.NaN) != false; + fail |= AnyFloatNaN64(floatNaN64, float.NaN) != false; + fail |= NoneFloatNaN64(floatNaN64, float.NaN) != true; + + Vector128 doubleNaN128 = Vector128.Create(double.NaN); + fail |= AllDoubleNaN128(doubleNaN128, double.NaN) != false; + fail |= AnyDoubleNaN128(doubleNaN128, double.NaN) != false; + fail |= NoneDoubleNaN128(doubleNaN128, double.NaN) != true; + + Vector64 doubleNaN64 = Vector64.Create(double.NaN); + fail |= AllDoubleNaN64(doubleNaN64, double.NaN) != false; + fail |= AnyDoubleNaN64(doubleNaN64, double.NaN) != false; + fail |= NoneDoubleNaN64(doubleNaN64, double.NaN) != true; + + return fail ? 101 : 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int CountByte128(Vector128 vector, byte value) + { + // ARM64-FULL-LINE: cmeq {{v[0-9]+}}.16b, {{v[0-9]+}}.16b, {{v[0-9]+}}.16b + // ARM64-FULL-LINE: ushr {{v[0-9]+}}.16b, {{v[0-9]+}}.16b, #7 + // ARM64-FULL-LINE: addv {{b[0-9]+}}, {{v[0-9]+}}.16b + // ARM64-NOT: AdvSimdExtractBitMask + // X64-FULL-LINE: {{v?}}pcmpeqb {{.*}} + // X64-FULL-LINE: {{v?}}pmovmskb {{.*}} + return Vector128.Count(vector, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int IndexOfByte128(Vector128 vector, byte value) + { + // ARM64-FULL-LINE: cmeq {{v[0-9]+}}.16b, {{v[0-9]+}}.16b, {{v[0-9]+}}.16b + // ARM64-FULL-LINE: bsl {{v[0-9]+}}.16b, {{v[0-9]+}}.16b, {{v[0-9]+}}.16b + // ARM64-FULL-LINE: uminv {{b[0-9]+}}, {{v[0-9]+}}.16b + // ARM64-NOT: AdvSimdExtractBitMask + // X64-FULL-LINE: {{v?}}pcmpeqb {{.*}} + // X64-FULL-LINE: {{v?}}pmovmskb {{.*}} + return Vector128.IndexOf(vector, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int LastIndexOfByte128(Vector128 vector, byte value) + { + // ARM64-FULL-LINE: cmeq {{v[0-9]+}}.16b, {{v[0-9]+}}.16b, {{v[0-9]+}}.16b + // ARM64-NOT: AdvSimdExtractBitMask + // X64-FULL-LINE: {{v?}}pcmpeqb {{.*}} + // X64-FULL-LINE: {{v?}}pmovmskb {{.*}} + return Vector128.LastIndexOf(vector, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool NoneByte128(Vector128 vector, byte value) + { + // ARM64-FULL-LINE: cmeq {{v[0-9]+}}.16b, {{v[0-9]+}}.16b, {{v[0-9]+}}.16b + // ARM64-FULL-LINE: umaxp {{v[0-9]+}}.4s, {{v[0-9]+}}.4s, {{v[0-9]+}}.4s + // ARM64-FULL-LINE: cset {{[wx][0-9]+}}, eq + return Vector128.None(vector, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AnyByte128(Vector128 vector, byte value) + { + // ARM64-FULL-LINE: cmeq {{v[0-9]+}}.16b, {{v[0-9]+}}.16b, {{v[0-9]+}}.16b + // ARM64-FULL-LINE: umaxp {{v[0-9]+}}.4s, {{v[0-9]+}}.4s, {{v[0-9]+}}.4s + // ARM64-FULL-LINE: cset {{[wx][0-9]+}}, ne + return Vector128.Any(vector, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AllByte128(Vector128 vector, byte value) + { + // ARM64-FULL-LINE: cmeq {{v[0-9]+}}.16b, {{v[0-9]+}}.16b, {{v[0-9]+}}.16b + // ARM64-NOT: AdvSimdExtractBitMask + return Vector128.All(vector, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int CountByte64(Vector64 vector, byte value) + { + // ARM64-FULL-LINE: cmeq {{v[0-9]+}}.8b, {{v[0-9]+}}.8b, {{v[0-9]+}}.8b + // ARM64-FULL-LINE: ushr {{v[0-9]+}}.8b, {{v[0-9]+}}.8b, #7 + // ARM64-FULL-LINE: addv {{b[0-9]+}}, {{v[0-9]+}}.8b + // ARM64-NOT: AdvSimdExtractBitMask + return Vector64.Count(vector, value); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int IndexOfByte64(Vector64 vector, byte value) => Vector64.IndexOf(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int LastIndexOfByte64(Vector64 vector, byte value) => Vector64.LastIndexOf(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool NoneByte64(Vector64 vector, byte value) => Vector64.None(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int CountByte512(Vector512 vector, byte value) => Vector512.Count(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int IndexOfByte512(Vector512 vector, byte value) => Vector512.IndexOf(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int LastIndexOfByte512(Vector512 vector, byte value) => Vector512.LastIndexOf(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int CountAllBitsSetInt128(Vector128 vector) + { + // ARM64-FULL-LINE: cmeq {{v[0-9]+}}.4s, {{v[0-9]+}}.4s, {{v[0-9]+}}.4s + // ARM64-FULL-LINE: ushr {{v[0-9]+}}.4s, {{v[0-9]+}}.4s, #31 + // ARM64-FULL-LINE: addv {{s[0-9]+}}, {{v[0-9]+}}.4s + // ARM64-NOT: AdvSimdExtractBitMask + // X64-FULL-LINE: {{v?}}pcmpeqd {{.*}} + // X64-FULL-LINE: {{v?}}movmskps {{.*}} + return Vector128.CountWhereAllBitsSet(vector); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int IndexOfAllBitsSetInt128(Vector128 vector) + { + // ARM64-FULL-LINE: cmeq {{v[0-9]+}}.4s, {{v[0-9]+}}.4s, {{v[0-9]+}}.4s + // ARM64-FULL-LINE: bsl {{v[0-9]+}}.4s, {{v[0-9]+}}.4s, {{v[0-9]+}}.4s + // ARM64-FULL-LINE: uminv {{s[0-9]+}}, {{v[0-9]+}}.4s + // ARM64-NOT: AdvSimdExtractBitMask + // X64-FULL-LINE: {{v?}}pcmpeqd {{.*}} + // X64-FULL-LINE: {{v?}}movmskps {{.*}} + return Vector128.IndexOfWhereAllBitsSet(vector); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int LastIndexOfAllBitsSetInt128(Vector128 vector) => Vector128.LastIndexOfWhereAllBitsSet(vector); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AnyAllBitsSetInt128(Vector128 vector) => Vector128.AnyWhereAllBitsSet(vector); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AllAllBitsSetInt128(Vector128 vector) => Vector128.AllWhereAllBitsSet(vector); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool NoneAllBitsSetInt128(Vector128 vector) => Vector128.NoneWhereAllBitsSet(vector); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int CountAllBitsSetInt64(Vector64 vector) => Vector64.CountWhereAllBitsSet(vector); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int IndexOfAllBitsSetInt64(Vector64 vector) => Vector64.IndexOfWhereAllBitsSet(vector); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int LastIndexOfAllBitsSetInt64(Vector64 vector) => Vector64.LastIndexOfWhereAllBitsSet(vector); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool NoneAllBitsSetInt64(Vector64 vector) => Vector64.NoneWhereAllBitsSet(vector); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int SetEvaluationOrderVector(int value) + { + s_evaluationOrderVector = Vector128.Create(value); + return 10; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int IndexOfEvaluationOrder() + { + s_evaluationOrderVector = Vector128.Zero; + return SetEvaluationOrderVector(42) + Vector128.IndexOf(s_evaluationOrderVector, 42); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int IndexOfWhereAllBitsSetEvaluationOrder() + { + s_evaluationOrderVector = Vector128.Zero; + return SetEvaluationOrderVector(-1) + Vector128.IndexOfWhereAllBitsSet(s_evaluationOrderVector); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int CountAllBitsSetFloat128(Vector128 vector) => Vector128.CountWhereAllBitsSet(vector); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static int IndexOfAllBitsSetFloat128(Vector128 vector) => Vector128.IndexOfWhereAllBitsSet(vector); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool NoneAllBitsSetFloat128(Vector128 vector) => Vector128.NoneWhereAllBitsSet(vector); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AllFloatNaN128(Vector128 vector, float value) => Vector128.All(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AnyFloatNaN128(Vector128 vector, float value) => Vector128.Any(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool NoneFloatNaN128(Vector128 vector, float value) => Vector128.None(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AllFloatNaN64(Vector64 vector, float value) => Vector64.All(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AnyFloatNaN64(Vector64 vector, float value) => Vector64.Any(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool NoneFloatNaN64(Vector64 vector, float value) => Vector64.None(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AllDoubleNaN128(Vector128 vector, double value) => Vector128.All(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AnyDoubleNaN128(Vector128 vector, double value) => Vector128.Any(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool NoneDoubleNaN128(Vector128 vector, double value) => Vector128.None(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AllDoubleNaN64(Vector64 vector, double value) => Vector64.All(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool AnyDoubleNaN64(Vector64 vector, double value) => Vector64.Any(vector, value); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool NoneDoubleNaN64(Vector64 vector, double value) => Vector64.None(vector, value); + } +} diff --git a/src/tests/JIT/opt/InstructionCombining/VectorHelperApis.csproj b/src/tests/JIT/opt/InstructionCombining/VectorHelperApis.csproj new file mode 100644 index 00000000000000..c71fc2fd396ff5 --- /dev/null +++ b/src/tests/JIT/opt/InstructionCombining/VectorHelperApis.csproj @@ -0,0 +1,16 @@ + + + true + + + None + True + + + + true + + + + + From 8e9adca496f2c5dca20a59b415de297b55aa840e Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Tue, 18 Aug 2026 09:27:48 +0000 Subject: [PATCH 2/3] Fix Vector512 helper API fallback --- src/coreclr/jit/hwintrinsic.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/hwintrinsic.cpp b/src/coreclr/jit/hwintrinsic.cpp index cb3e0b80078aaa..2e476b299e6c1b 100644 --- a/src/coreclr/jit/hwintrinsic.cpp +++ b/src/coreclr/jit/hwintrinsic.cpp @@ -3033,7 +3033,7 @@ GenTree* Compiler::impXplatIntrinsic(NamedIntrinsic intrinsic, auto vectorFirstArgMatchesSimdSize = [this, sig, simdSize]() { // Vector2/3/4 and the fixed-size Vector64/128 helpers share some method names. Make sure this importer // only handles the generic Vector64/Vector128 methods whose first argument has the expected SIMD size. - if (sig->sigInst.methInstCount != 1) + if (((simdSize != 8) && (simdSize != 16)) || (sig->sigInst.methInstCount != 1)) { return false; } From 66fd15431cab56508d862d26366ae0b40f291c55 Mon Sep 17 00:00:00 2001 From: Jonathan Davies Date: Tue, 1 Sep 2026 09:11:37 +0000 Subject: [PATCH 3/3] Fix Vector helper API imports on unsupported targets --- src/coreclr/jit/hwintrinsic.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/coreclr/jit/hwintrinsic.cpp b/src/coreclr/jit/hwintrinsic.cpp index 2e476b299e6c1b..4737e1e2c91c26 100644 --- a/src/coreclr/jit/hwintrinsic.cpp +++ b/src/coreclr/jit/hwintrinsic.cpp @@ -3030,6 +3030,7 @@ GenTree* Compiler::impXplatIntrinsic(NamedIntrinsic intrinsic, bool leftUpper = false; bool rightUpper = false; +#if defined(TARGET_ARM64) || defined(TARGET_XARCH) auto vectorFirstArgMatchesSimdSize = [this, sig, simdSize]() { // Vector2/3/4 and the fixed-size Vector64/128 helpers share some method names. Make sure this importer // only handles the generic Vector64/Vector128 methods whose first argument has the expected SIMD size. @@ -3049,6 +3050,11 @@ GenTree* Compiler::impXplatIntrinsic(NamedIntrinsic intrinsic, return varTypeIsSIMD(argType) && (genTypeSize(argType) == simdSize); }; +#else + auto vectorFirstArgMatchesSimdSize = []() { + return false; + }; +#endif auto vectorValueEqualsMask = [this, simdSize](GenTree* vector, GenTree* value, var_types simdBaseType) -> GenTree* { var_types simdType = getSIMDTypeForSize(simdSize);