Uh oh!
There was an error while loading. Please reload this page.
#78303 Add transformation ~v1 & v2 to VectorXxx.AndNot(v1, v2) - #81993
Conversation
ghost
commented
Feb 11, 2023
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch, @kunalspathak Issue DetailsI created a draft for the issue.
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| case NI_SSE_And: | ||
| case NI_SSE2_And: | ||
| case NI_AVX_And: | ||
| case NI_AVX2_And: |
There was a problem hiding this comment.
What about Vector128/256_And and AdvSimd ?
There was a problem hiding this comment.
Vector64/128/256_And don't exist outside of import at the moment so they don't need to be handled.
AdvSimd should be since we want parity between xarch and arm.
| case NI_AVX_And: | ||
| case NI_AVX2_And: | ||
| { | ||
| if (node->GetOperandCount() != 2) |
There was a problem hiding this comment.
Shouldn't ever not be 2. If it was, we'd have a buggy node.
There was a problem hiding this comment.
Use an assert then instead?
| if (op1->OperIs(GT_HWINTRINSIC)) | ||
| { | ||
| rhs = op2; | ||
| inner_hw = op1->AsHWIntrinsic(); | ||
| } | ||
| // Transforms v2 & (~v1) to VectorXxx.AndNot(v1, v2) | ||
| else if (op2->OperIs(GT_HWINTRINSIC)) | ||
| { | ||
| rhs = op1; | ||
| inner_hw = op2->AsHWIntrinsic(); | ||
| } | ||
| else | ||
| { | ||
| return node; | ||
| } |
There was a problem hiding this comment.
This is going to miss the optimization for cases like: ((x & y) & ~z)
You're going to need to check that it is a hwintrinsic and that it is the relevant xor (xarch and arm) or not (arm only) node.
There was a problem hiding this comment.
There is also potentially a concern around side effects and ensuring that ~x & y, which must be represented as gtNewSimdBinOpNode(AND_NOT, y, x, ...) preserves side effects with regards to x being evaluted before y.
There was a problem hiding this comment.
I have resolved some comments and pushed them to make sure I got you right.
Could you please give me a hint how to treat not for arm and how to test it and how to handle the sideeffect case?
| if ((inner_hw->GetOperandCount() != 2) || (!inner_hw->Op(2)->IsVectorAllBitsSet())) | ||
| { | ||
| return node; | ||
| } |
There was a problem hiding this comment.
Would be better to check this as part of handling _Xor below, that way you don't need to check the operand count and its easier for the general logic to support AdvSimd_Not on Arm64.
| var_types hw_type = node->TypeGet(); | ||
| CorInfoType hw_coretype = node->GetSimdBaseJitType(); | ||
| unsigned int hw_simdsize = node->GetSimdSize(); |
There was a problem hiding this comment.
We refer to these as just simdType, simdBaseJitType, and simdSize almost everywhere else in the JIT.
| GenTreeHWIntrinsic* xor_hw = op1->AsHWIntrinsic(); | ||
| switch (xor_hw->GetHWIntrinsicId()) | ||
| { | ||
| #if defined(TARGET_XARCH) || defined(TARGET_ARM64) |
There was a problem hiding this comment.
This is unnecessary, you're already in a larger identical ifdef from L10873.
That being said, the larger identical ifdef on L10873 should also be unnecessary given we're in a greater #ifdef FEATURE_HW_INTRINSICS
| } | ||
| // Transforms v2 & (~v1) to VectorXxx.AndNot(v2, v1) | ||
| if (op2->OperIs(GT_HWINTRINSIC)) |
There was a problem hiding this comment.
This check is going to miss the opt if we have something like ((x ^ AllBitsSet) & (y ^ z). Such a tree could have been transformed into AndNot((y ^ z), x)
In general you're going to need to match (op1 ^ AllBitsSet) up front before determining if its a match and then if that fails do the same check for (op2 ^ AllBitsSet).
For Arm64, you'll also need to directly check for ~op1 or ~op2 (since NI_AdvSimd_Not exists).
There are some things we could do to make this overall simpler, but they are slightly more involved changes.
There was a problem hiding this comment.
I'd, in general, recommend extracting some of this to a helper.
For example, you could define something like:
genTreeOps GenTreeHWIntrinsic::HWOperGet()
{
switch (GetHWIntrinsicId())
{
#if defined(TARGET_XARCH)
case NI_SSE_And:
case NI_SSE2_And:
case NI_AVX_And:
case NI_AVX2_And:
#elif defined(TARGET_ARM64)
case NI_AdvSimd_And:
#endif
{
returnGT_AND;
}
#if defined(TARGET_ARM64)
case NI_AdvSimd_Not:
{
returnGT_NOT;
}
#endif
#if defined(TARGET_XARCH)
case NI_SSE_Xor:
case NI_SSE2_Xor:
case NI_AVX_Xor:
case NI_AVX2_Xor:
#elif defined(TARGET_ARM64)
case NI_AdvSimd_Xor:
#endif
{
returnGT_XOR;
}
// TODO: Handle other casesdefault:
{
returnGT_NONE;
}
}
}Such a helper allows you to instead switch over the genTreeOps equivalent. So you could have something like:
switch (node->HWOperGet())
{
caseGT_AND:
{
GenTree* op1 = node->Op(1);
GenTree* op2 = node->Op(2);
GenTree* lhs = nullptr;
GenTree* rhs = nullptr;
if (op1->OperIsHWIntrinsic())
{
// Try handle: ~op1 & op2
GenTreeHWIntrinsic* hw = op1->AsHWIntrinsic();
genTreeOps hwOper = hw->HWOperGet();
if (hwOper == GT_NOT)
{
lhs = op2;
rhs = op1;
}
elseif (op1Oper == GT_XOR)
{
GenTree* hwOp1 = hw->Op(1);
GenTree* hwOp2 = hw->Op(2);
if (hwOp1->IsVectorAllBitsSet())
{
lhs = op2;
rhs = hwOp2;
}
elseif (hwOp2->IsVectorAllBitsSet())
{
lhs = op2;
rhs = hwOp1;
}
}
}
if ((lhs == nullptr) && op2->OperIsHWIntrinsic())
{
// Try handle: op1 & ~op2
GenTreeHWIntrinsic* hw = op2->AsHWIntrinsic();
genTreeOps hwOper = hw->HWOperGet();
if (hwOper == GT_NOT)
{
lhs = op1;
rhs = op2;
}
elseif (op1Oper == GT_XOR)
{
GenTree* hwOp1 = hw->Op(1);
GenTree* hwOp2 = hw->Op(2);
if (hwOp1->IsVectorAllBitsSet())
{
lhs = op1;
rhs = hwOp2;
}
elseif (hwOp2->IsVectorAllBitsSet())
{
lhs = op1;
rhs = hwOp1;
}
}
}
if (lhs == nullptr)
{
break;
}
GenTree* andnNode = gtNewSimdBinOpNode(GT_AND_NOT, simdType, lhs, rhs, simdBaseJitType, simdSize, true);
DEBUG_DESTROY_NODE(node);
INDEBUG(andnNode->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED);
return andnNode;
}
default:
{
break;
}
}You could of course also extract the NOT op vs op XOR AllBitsSet matching logic to reduce duplication as well.
There was a problem hiding this comment.
Longer term, I think we may want to introduce a "fake" Isa_Not hwintrinsic id for xarch. That would allow morph to transform x ^ AllBitsSet into Isa_Not and then would in turn allow this case to be simplified in its pattern checks.
We may also want to normalize cases like Sse_Xor, Sse2_Xor, and AdvSimd_Xor into Vector128_Xor, so we don't need to consider xplat differences. But that will also involve significant refactorings, far more so than introducing a HWOperGet helper for the time being.
| genTreeOps GenTreeHWIntrinsic::HWOperGet() | ||
| { | ||
| switch (GetHWIntrinsicId()) |
There was a problem hiding this comment.
@tannergooding do you think we can then (not necessarily in this PR) add this to the table where intrinsics are defined?
There was a problem hiding this comment.
We should be able to do so, yes.
However, I rather think we'd want to represent it just a little bit differently to avoid bloating the metadata tables given most intrinsics end up as none.
| // Transforms: | ||
| // 1.(~v1 & v2) to VectorXxx.AndNot(v1, v2) | ||
| // 2.(v1 & (~v2)) to VectorXxx.AndNot(v1, v2) |
Uh oh!
There was an error while loading. Please reload this page.
JulieLeeMSFT
commented
Aug 7, 2023
@SkiFoD, please resolve the merge conflict. .NET 8 rc1 snap is 8/14. |
SkiFoD
commented
Aug 14, 2023
OK, I will look at it as soon as possible. |
EgorBo
commented
Sep 4, 2023
@SkiFoD thanks! sorry for the delay, there was also a small issue in the codegen that I fixed |
The new logic introduced in dotnet#81993 would swap the LHS and RHS of the operands without any additional checks for side effects. Fixdotnet#91855
I created a draft for the issue #78303
The code converts ~v1 & v2 to VectorXxx.AndNot(v1, v2)