Arm64: Improve support for HW_Flag_ReturnsPerElementMask#128326
Arm64: Improve support for HW_Flag_ReturnsPerElementMask#128326snickolls-arm wants to merge 15 commits into
Conversation
When wrapping an intrinsic node that has an embedded mask with a ConditionalSelect, ensure that the constant node in op3 has a mask type when the intrinsic has the HW_Flag_ReturnsPerElementMask flag. Build out further support for ConditionalSelect_Predicates, and use this to wrap nodes with HW_Flag_ReturnsPerElementMask. Add GenTree::IsSelectZero and update various areas in HW intrinsic codegen to ensure this intrinsic assembles correctly. Use a tree visitor for assigning `TYP_MASK` to intrinsics that have `HW_Flag_ReturnsPerElementMask`. The current version of `impHWIntrinsic` does not process child nodes of the tree it returns for mask types, only the root node.
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
|
FYI if it's not known, assert in PR testing |
I didn't see thanks, will take a look. |
There was a problem hiding this comment.
Pull request overview
This PR extends Arm64 SVE support for intrinsics that return per-element masks by introducing shared recognition of SVE conditional-select variants, adding a “select-zero” helper, updating containment/codegen to treat mask-zero similarly to vector-zero, and updating constant-folding / emitter behavior to match SVE predicate element semantics.
Changes:
- Adds
HWIntrinsicInfo::IsSveConditionalSelectandGenTree::IsSelectZero, and updates lowering/LSRA/codegen paths to use them. - Updates SVE mask creation intrinsics to be treated as returning per-element masks and adjusts import/constant construction accordingly.
- Adjusts SVE emitter options handling and updates/extends a JIT test to validate the new mask element representation.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tests/JIT/opt/SVE/ConditionalSelectConstants.cs | Updates expectations and adds a smoke check validating mask element values. |
| src/coreclr/jit/simd.h | Changes mask-to-vector constant evaluation semantics on Arm64 and adds helper to convert elementwise mask values to bitwise masks for folding. |
| src/coreclr/jit/lsraarm64.cpp | Treats both vector and predicate conditional-select forms uniformly when identifying embedded/contained selects. |
| src/coreclr/jit/lowerarmarch.cpp | Expands lowering/containment to include predicate conditional-select and uses IsSelectZero for zero-detection. |
| src/coreclr/jit/hwintrinsiclistarm64sve.h | Marks SVE CreateTrue/FalseMask intrinsics as returning per-element masks; updates containment flag for predicate select. |
| src/coreclr/jit/hwintrinsiccodegenarm64.cpp | Switches embedded-mask setup and containment checks from vector-zero to select-zero, enabling mask-zero handling. |
| src/coreclr/jit/hwintrinsicarm64.cpp | Imports SVE false masks via gtNewZeroConNode and updates true-mask constant folding to use mask constants. |
| src/coreclr/jit/hwintrinsic.h | Introduces IsSveConditionalSelect helper for consistent conditional-select recognition. |
| src/coreclr/jit/gentree.h | Adds GenTree::IsSelectZero helper for conditional-select zero detection across vectors/masks. |
| src/coreclr/jit/gentree.cpp | Extends gtNewZeroConNode to create mask-zero constants and updates folding for SVE mask representation. |
| src/coreclr/jit/emitarm64sve.cpp | Forces byte arrangement for predicate encodings where required and relaxes/adjusts scalable option assertions. |
Comments suppressed due to low confidence (1)
src/coreclr/jit/lowerarmarch.cpp:3636
TryContainingCselOpnow accepts bothNI_Sve_ConditionalSelectandNI_Sve_ConditionalSelect_Predicates, but the containment fast-path is still gated onchildNode->Op(3)->IsVectorZero(). For the predicate variant, op3 is expected to be a mask-zero (pfalse) rather than a vector-zero, so this will never trigger and makes the new broader check ineffective for mask selects.
if (!HWIntrinsicInfo::IsSveConditionalSelect(childNode->GetHWIntrinsicId()))
{
return false;
}
if (childNode->Op(2)->IsEmbMaskOp())
{
// Do not optimise if the conditional select node is added to embed the mask for its op2. Such conditional
// select nodes are optimised out while emitting the embedded variant of an instruction associated with op2.
assert(childNode->Op(2)->isContained());
return false;
}
bool canContain = false;
var_types simdBaseType = parentNode->GetSimdBaseType();
if (childNode->Op(3)->IsVectorZero())
{
| #ifdef TARGET_ARM64 | ||
| // TODO-SVE: We want to unify this output to 'AllBitsSet' as in other | ||
| // architectures, so we can benefit fully from optimizations on this value. | ||
| memset(&output, 0x1, 1); | ||
| #else | ||
| memset(&output, 0xFF, sizeof(TBase)); | ||
| #endif |
| { | ||
| // Import as a constant vector 0 | ||
| GenTreeVecCon* vecCon = gtNewVconNode(retType); | ||
| vecCon->gtSimdVal = simd_t::Zero(); | ||
| retNode = vecCon; | ||
| retNode = gtNewZeroConNode(retType); | ||
| break; |
| // Enforce invariant HW_Flag_ReturnPerElementMask <==> node->TypeIs(TYP_MASK) | ||
| // This should happen at all stages of the compiler, but it's especially important to check here, |
@snickolls-arm, as an FYI this is going to change a bit with #130049. It is effectively merging most of the tables for the xplat intrinsics into a new combined table and the import logic itself into a combined handler as well. The handling between That should make it a bit simpler to lightup for SVE, as you'll not need to extend or duplicate the tables again or add a bunch of |
| case NI_Sve_CreateTrueMaskUInt64: | ||
| { | ||
| assert(sig->numArgs == 1); | ||
| assert(retType == TYP_MASK); |
There was a problem hiding this comment.
Not quite sure I understand this assert?
Sve.CreateTrueMaskByte is a managed API, and so I'd expect it to be producing a TYP_SIMD, same as for NI_Sve_CreateFalseMask.
So we should either produce a CNS_VEC or a CvtMaskToVector(truemaskall(op1)). Downstream consumers that expect a mask will then have CvtVectorToMask(CNS_VEC) which folds to CNS_MSK or CvtVectorToMask(CvtMaskToVector(truemaskall(op1)) which folds to just truemaskall(op1)
There was a problem hiding this comment.
It's due to the way it's wired up with the main import function:
runtime/src/coreclr/jit/hwintrinsic.cpp
Lines 2417 to 2422 in 8fe2929
runtime/src/coreclr/jit/hwintrinsic.cpp
Lines 2872 to 2876 in 8fe2929
It will pass
nodeRetType == TYP_MASK to impSpecialIntrinsic for any intrinsics that have HW_Flag_ReturnsPerElementMask. Then we insert the CvtMaskToVector conversion on anything that reaches the end that has TYP_MASK.
Thanks for the heads up. This should reduce the diffs I have for implementing the |
When wrapping an intrinsic node that has an embedded mask with a
ConditionalSelect, ensure that the constant node in op3 has a mask type when the intrinsic has theHW_Flag_ReturnsPerElementMaskflag.Build out further support for
ConditionalSelect_Predicates, and use this to wrap nodes withHW_Flag_ReturnsPerElementMask. AddGenTree::IsSelectZeroand update various areas in HW intrinsic codegen to ensure this intrinsic assembles correctly.