-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Add pattern matching for SVE intrinsics that operate on mask operands #114438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
c5922a1
3ee0230
782d7fd
8793f72
0b56784
0378754
ff2df6c
2cec188
f067baa
eae2622
e28622a
713ab96
0637900
0b4e5db
c77c83e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9218,6 +9218,15 @@ GenTree* Compiler::fgOptimizeHWIntrinsic(GenTreeHWIntrinsic* node) | |
| } | ||
| } | ||
|
|
||
| #ifdef TARGET_ARM64 | ||
| optimizedTree = fgMorphTryUseAllMaskVariant(node); | ||
| if (optimizedTree != nullptr) | ||
|
||
| { | ||
| optimizedTree->SetMorphed(this); | ||
| return optimizedTree; | ||
| } | ||
| #endif | ||
|
|
||
| NamedIntrinsic intrinsicId = node->GetHWIntrinsicId(); | ||
| var_types retType = node->TypeGet(); | ||
| CorInfoType simdBaseJitType = node->GetSimdBaseJitType(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,206 @@ | ||||
| // Licensed to the .NET Foundation under one or more agreements. | ||||
| // The .NET Foundation licenses this file to you under the MIT license. | ||||
|
|
||||
| /*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | ||||
| XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | ||||
| XX XX | ||||
| XX Arm64 Specific Morph XX | ||||
| XX XX | ||||
| XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | ||||
| XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | ||||
| */ | ||||
|
|
||||
| #include "jitpch.h" | ||||
| #ifdef _MSC_VER | ||||
| #pragma hdrstop | ||||
| #endif | ||||
|
|
||||
| #ifdef FEATURE_MASKED_HW_INTRINSICS | ||||
|
|
||||
| //------------------------------------------------------------------------ | ||||
| // HasAllMaskVariant: Does this intrinsic have a variant where all of it's operands | ||||
| // are mask types? | ||||
| // | ||||
| // Return Value: | ||||
| // true if an all-mask variant exists for the intrinsic, else false. | ||||
| // | ||||
| bool GenTreeHWIntrinsic::HasAllMaskVariant() | ||||
| { | ||||
| switch (GetHWIntrinsicId()) | ||||
| { | ||||
| // ZIP1 <Pd>.<T>, <Pn>.<T>, <Pm>.<T> | ||||
|
||||
| // ZIP2 <Pd>.<T>, <Pn>.<T>, <Pm>.<T> | ||||
| // UZP1 <Pd>.<T>, <Pn>.<T>, <Pm>.<T> | ||||
| // UZP2 <Pd>.<T>, <Pn>.<T>, <Pm>.<T> | ||||
| // TRN1 <Pd>.<T>, <Pn>.<T>, <Pm>.<T> | ||||
| // TRN2 <Pd>.<T>, <Pn>.<T>, <Pm>.<T> | ||||
| // REV <Pd>.<T>, <Pn>.<T> | ||||
| case NI_Sve_ZipHigh: | ||||
| case NI_Sve_ZipLow: | ||||
| case NI_Sve_UnzipOdd: | ||||
| case NI_Sve_UnzipEven: | ||||
| case NI_Sve_TransposeEven: | ||||
| case NI_Sve_TransposeOdd: | ||||
| case NI_Sve_ReverseElement: | ||||
| return true; | ||||
|
|
||||
| default: | ||||
| return false; | ||||
| } | ||||
| } | ||||
|
|
||||
| //------------------------------------------------------------------------ | ||||
| // canMorphVectorOperandToMask: Can this vector operand be converted to a | ||||
| // node with type TYP_MASK easily? | ||||
| // | ||||
| bool Compiler::canMorphVectorOperandToMask(GenTree* node) | ||||
|
||||
| { | ||||
| return varTypeIsMask(node) || node->OperIsConvertMaskToVector() || node->IsVectorZero(); | ||||
| } | ||||
|
|
||||
| //------------------------------------------------------------------------ | ||||
| // canMorphAllVectorOperandsToMasks: Can all vector operands to this node | ||||
| // be converted to a node with type | ||||
| // TYP_MASK easily? | ||||
| // | ||||
| bool Compiler::canMorphAllVectorOperandsToMasks(GenTreeHWIntrinsic* node) | ||||
| { | ||||
| bool allMaskConversions = true; | ||||
| for (size_t i = 1; i <= node->GetOperandCount() && allMaskConversions; i++) | ||||
| { | ||||
| allMaskConversions &= canMorphVectorOperandToMask(node->Op(i)); | ||||
| } | ||||
|
|
||||
| return allMaskConversions; | ||||
| } | ||||
|
|
||||
| //------------------------------------------------------------------------ | ||||
| // doMorphVectorOperandToMask: Morph a vector node that is close to a mask | ||||
| // node into a mask node. | ||||
| // | ||||
| // Return value: | ||||
| // The morphed tree, or nullptr if the transform is not applicable. | ||||
| // | ||||
| GenTree* Compiler::doMorphVectorOperandToMask(GenTree* node, GenTreeHWIntrinsic* parent) | ||||
| { | ||||
| if (varTypeIsMask(node)) | ||||
| { | ||||
| // Already a mask, nothing to do. | ||||
| return node; | ||||
| } | ||||
| else if (node->OperIsConvertMaskToVector()) | ||||
| { | ||||
| // Replace node with op1. | ||||
| return node->AsHWIntrinsic()->Op(1); | ||||
| } | ||||
| else if (node->IsVectorZero()) | ||||
| { | ||||
| // Morph the vector of zeroes into mask of zeroes. | ||||
| GenTree* mask = gtNewSimdAllFalseMaskNode(parent->GetSimdSize()); | ||||
| mask->SetMorphed(this); | ||||
| return mask; | ||||
| } | ||||
|
|
||||
| return nullptr; | ||||
| } | ||||
|
|
||||
| //----------------------------------------------------------------------------------------------------- | ||||
| // fgMorphTryUseAllMaskVariant: For NamedIntrinsics that have a variant where all operands are | ||||
| // mask nodes. If all operands to this node are 'suggesting' that they | ||||
| // originate closely from a mask, but are of vector types, then morph the | ||||
| // operands as appropriate to use mask types instead. 'Suggesting' | ||||
| // is defined by the canMorphVectorOperandToMask function. | ||||
| // | ||||
| // Arguments: | ||||
| // tree - The HWIntrinsic to try and optimize. | ||||
| // | ||||
| // Return Value: | ||||
| // The fully morphed tree if a change was made, else nullptr. | ||||
| // | ||||
| GenTree* Compiler::fgMorphTryUseAllMaskVariant(GenTreeHWIntrinsic* node) | ||||
| { | ||||
| if (node->HasAllMaskVariant() && canMorphAllVectorOperandsToMasks(node)) | ||||
|
||||
| { | ||||
| for (size_t i = 1; i <= node->GetOperandCount(); i++) | ||||
| { | ||||
| node->Op(i) = doMorphVectorOperandToMask(node->Op(i), node); | ||||
| } | ||||
|
|
||||
| node->gtType = TYP_MASK; | ||||
| return node; | ||||
| } | ||||
|
|
||||
| if (node->OperIsHWIntrinsic(NI_Sve_ConditionalSelect)) | ||||
| { | ||||
| GenTree* mask = node->Op(1); | ||||
| GenTree* left = node->Op(2); | ||||
| GenTree* right = node->Op(3); | ||||
|
|
||||
| if (left->OperIsHWIntrinsic()) | ||||
| { | ||||
| assert(canMorphVectorOperandToMask(mask)); | ||||
|
|
||||
| if (canMorphAllVectorOperandsToMasks(left->AsHWIntrinsic())) | ||||
| { | ||||
| // At this point we know the 'left' node is a HWINTRINSIC node and all of its operands look like | ||||
| // mask nodes. | ||||
| // | ||||
| // The ConditionalSelect could be substituted for the named intrinsic in it's 'left' operand and | ||||
| // transformed to a mask-type operation for some named intrinsics. Doing so will encourage codegen | ||||
| // to emit predicate variants of instructions rather than vector variants, and we can lose some | ||||
| // unnecessary mask->vector conversion nodes. | ||||
| GenTreeHWIntrinsic* actualOp = left->AsHWIntrinsic(); | ||||
|
|
||||
| switch (actualOp->GetHWIntrinsicId()) | ||||
| { | ||||
| // AND <Pd>.B, <Pg>/Z, <Pn>.B, <Pm>.B | ||||
| // BIC <Pd>.B, <Pg>/Z, <Pn>.B, <Pm>.B | ||||
| // EOR <Pd>.B, <Pg>/Z, <Pn>.B, <Pm>.B | ||||
| // ORR <Pd>.B, <Pg>/Z, <Pn>.B, <Pm>.B | ||||
| case NI_Sve_And: | ||||
|
||||
| assert(isVectorRegister(reg3)); // ddddd |
The mask variant of this intrinsic has an embedded mask, but it's required for this instruction instead of optional, so there would also need to be some handling of this edge case in codegen to make sure it definitely wraps the mask variant in ConditionalSelect. It feels like there should be a separate set of flags for when the intrinsic is TYP_MASK or TYP_SIMD. E.g. HW_Flag_MaskVariant(Optional)EmbeddedMaskOperation, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should have a separate intrinsics And_Predicates (and likewise for other APIs that have predicates variant). They are added in the section "Special intrinsics that are generated during importing or lowering". And_Predicates should have HW_Flag_EmbeddedMaskedOperation. We can have flag HW_Flag_AllMaskVariant on SVE_And intrinsics, to detect it in morph if this can be transformed into And_Predicates variant.
We come here in the morph and see And(Vector, Vector). If operands are mask, we can transform the node into And_Predicates(Mask, Mask). During lowering, we can then transform it into CndSel(AllTrue, And_Predicates(Mask, Mask), Zero) and codegen will handle generating the predicated version of And (predicates).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds much better than what I was thinking, I'll try and implement this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've pushed this implementation, it seems simpler. N.B. we've run out of flag space now as I've taken the last bit for HW_Flag_HasMaskVariant. We might need to think about expanding the flag space for SVE2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've pushed this implementation, it seems simpler. N.B. we've run out of flag space now as I've taken the last bit for
HW_Flag_HasMaskVariant. We might need to think about expanding the flag space for SVE2.
Added #115474 to track this
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <Project> | ||
| <Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.props, $(MSBuildThisFileDirectory)..))" /> | ||
| <PropertyGroup> | ||
| <NoWarn>$(NoWarn);SYSLIB5003</NoWarn> | ||
| <CLRTestTargetUnsupported Condition="'$(TargetArchitecture)' != 'arm64' or '$(TargetOS)' == 'osx' or '$(RuntimeFlavor)' == 'mono'">true</CLRTestTargetUnsupported> | ||
| </PropertyGroup> | ||
| </Project> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.