-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Ensure Vector<T>.op_Multiply is handled as an intrinsic in appropriate cases #49503
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 all commits
fca9b85
24373c5
9d7c8c5
2b5a43e
b9c77d7
1b1715d
a79a1cb
56c210d
2d72fd0
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 |
|---|---|---|
|
|
@@ -880,56 +880,154 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, | |
|
|
||
| case NI_VectorT128_op_Multiply: | ||
| { | ||
| assert(baseType == TYP_INT); | ||
|
|
||
| NamedIntrinsic hwIntrinsic = NI_Illegal; | ||
| GenTree** broadcastOp = nullptr; | ||
|
|
||
| if (compOpportunisticallyDependsOn(InstructionSet_SSE41)) | ||
| if (varTypeIsArithmetic(op1->TypeGet())) | ||
| { | ||
| hwIntrinsic = NI_SSE41_MultiplyLow; | ||
| broadcastOp = &op1; | ||
| } | ||
| else | ||
| else if (varTypeIsArithmetic(op2->TypeGet())) | ||
| { | ||
| // op1Dup = op1 | ||
| GenTree* op1Dup; | ||
| op1 = impCloneExpr(op1, &op1Dup, clsHnd, (unsigned)CHECK_SPILL_ALL, | ||
| nullptr DEBUGARG("Clone op1 for Vector<T>.Multiply")); | ||
| broadcastOp = &op2; | ||
| } | ||
|
|
||
| // op2Dup = op2 | ||
| GenTree* op2Dup; | ||
| op2 = impCloneExpr(op2, &op2Dup, clsHnd, (unsigned)CHECK_SPILL_ALL, | ||
| nullptr DEBUGARG("Clone op2 for Vector<T>.Multiply")); | ||
| if (broadcastOp != nullptr) | ||
| { | ||
| *broadcastOp = gtNewSimdCreateBroadcastNode(simdType, *broadcastOp, baseType, simdSize, | ||
| /* isSimdAsHWIntrinsic */ true); | ||
| } | ||
|
|
||
| // op1 = Sse2.ShiftRightLogical128BitLane(op1, 4) | ||
| op1 = gtNewSimdAsHWIntrinsicNode(retType, op1, gtNewIconNode(4, TYP_INT), | ||
| NI_SSE2_ShiftRightLogical128BitLane, baseType, simdSize); | ||
| switch (baseType) | ||
| { | ||
| case TYP_SHORT: | ||
| case TYP_USHORT: | ||
| { | ||
| hwIntrinsic = NI_SSE2_MultiplyLow; | ||
| break; | ||
| } | ||
|
|
||
| // op2 = Sse2.ShiftRightLogical128BitLane(op1, 4) | ||
| op2 = gtNewSimdAsHWIntrinsicNode(retType, op2, gtNewIconNode(4, TYP_INT), | ||
| NI_SSE2_ShiftRightLogical128BitLane, baseType, simdSize); | ||
| case TYP_INT: | ||
| case TYP_UINT: | ||
| { | ||
| if (compOpportunisticallyDependsOn(InstructionSet_SSE41)) | ||
| { | ||
| hwIntrinsic = NI_SSE41_MultiplyLow; | ||
| } | ||
| else | ||
| { | ||
| // op1Dup = op1 | ||
| GenTree* op1Dup; | ||
| op1 = impCloneExpr(op1, &op1Dup, clsHnd, (unsigned)CHECK_SPILL_ALL, | ||
| nullptr DEBUGARG("Clone op1 for Vector<T>.Multiply")); | ||
|
|
||
| // op2Dup = op2 | ||
| GenTree* op2Dup; | ||
| op2 = impCloneExpr(op2, &op2Dup, clsHnd, (unsigned)CHECK_SPILL_ALL, | ||
| nullptr DEBUGARG("Clone op2 for Vector<T>.Multiply")); | ||
|
|
||
| // op1 = Sse2.ShiftRightLogical128BitLane(op1, 4) | ||
| op1 = | ||
| gtNewSimdAsHWIntrinsicNode(retType, op1, gtNewIconNode(4, TYP_INT), | ||
| NI_SSE2_ShiftRightLogical128BitLane, baseType, simdSize); | ||
|
|
||
| // op2 = Sse2.ShiftRightLogical128BitLane(op1, 4) | ||
| op2 = | ||
| gtNewSimdAsHWIntrinsicNode(retType, op2, gtNewIconNode(4, TYP_INT), | ||
| NI_SSE2_ShiftRightLogical128BitLane, baseType, simdSize); | ||
|
|
||
| // op2 = Sse2.Multiply(op2.AsUInt64(), op1.AsUInt64()).AsInt32() | ||
| op2 = gtNewSimdAsHWIntrinsicNode(retType, op2, op1, NI_SSE2_Multiply, TYP_ULONG, | ||
| simdSize); | ||
|
|
||
| // op2 = Sse2.Shuffle(op2, (0, 0, 2, 0)) | ||
| op2 = gtNewSimdAsHWIntrinsicNode(retType, op2, gtNewIconNode(SHUFFLE_XXZX, TYP_INT), | ||
| NI_SSE2_Shuffle, baseType, simdSize); | ||
|
|
||
| // op1 = Sse2.Multiply(op1Dup.AsUInt64(), op2Dup.AsUInt64()).AsInt32() | ||
| op1 = gtNewSimdAsHWIntrinsicNode(retType, op1Dup, op2Dup, NI_SSE2_Multiply, TYP_ULONG, | ||
| simdSize); | ||
|
|
||
| // op1 = Sse2.Shuffle(op1, (0, 0, 2, 0)) | ||
| op1 = gtNewSimdAsHWIntrinsicNode(retType, op1, gtNewIconNode(SHUFFLE_XXZX, TYP_INT), | ||
| NI_SSE2_Shuffle, baseType, simdSize); | ||
|
|
||
| // result = Sse2.UnpackLow(op1, op2) | ||
| hwIntrinsic = NI_SSE2_UnpackLow; | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| // op2 = Sse2.Multiply(op2.AsUInt64(), op1.AsUInt64()).AsInt32() | ||
| op2 = gtNewSimdAsHWIntrinsicNode(retType, op2, op1, NI_SSE2_Multiply, TYP_ULONG, simdSize); | ||
| case TYP_FLOAT: | ||
| { | ||
| hwIntrinsic = NI_SSE_Multiply; | ||
| break; | ||
| } | ||
|
|
||
| // op2 = Sse2.Shuffle(op2, (0, 0, 2, 0)) | ||
| op2 = gtNewSimdAsHWIntrinsicNode(retType, op2, gtNewIconNode(SHUFFLE_XXZX, TYP_INT), | ||
| NI_SSE2_Shuffle, baseType, simdSize); | ||
| case TYP_DOUBLE: | ||
| { | ||
| hwIntrinsic = NI_SSE2_Multiply; | ||
| break; | ||
| } | ||
|
|
||
| // op1 = Sse2.Multiply(op1Dup.AsUInt64(), op2Dup.AsUInt64()).AsInt32() | ||
| op1 = | ||
| gtNewSimdAsHWIntrinsicNode(retType, op1Dup, op2Dup, NI_SSE2_Multiply, TYP_ULONG, simdSize); | ||
| default: | ||
| { | ||
| unreached(); | ||
| } | ||
| } | ||
|
|
||
| assert(hwIntrinsic != NI_Illegal); | ||
| return gtNewSimdAsHWIntrinsicNode(retType, op1, op2, hwIntrinsic, baseType, simdSize); | ||
| } | ||
|
|
||
| // op1 = Sse2.Shuffle(op1, (0, 0, 2, 0)) | ||
| op1 = gtNewSimdAsHWIntrinsicNode(retType, op1, gtNewIconNode(SHUFFLE_XXZX, TYP_INT), | ||
| NI_SSE2_Shuffle, baseType, simdSize); | ||
| case NI_VectorT256_op_Multiply: | ||
| { | ||
| NamedIntrinsic hwIntrinsic = NI_Illegal; | ||
| GenTree** broadcastOp = nullptr; | ||
|
|
||
| // result = Sse2.UnpackLow(op1, op2) | ||
| hwIntrinsic = NI_SSE2_UnpackLow; | ||
| if (varTypeIsArithmetic(op1->TypeGet())) | ||
| { | ||
| broadcastOp = &op1; | ||
| } | ||
| else if (varTypeIsArithmetic(op2->TypeGet())) | ||
| { | ||
| broadcastOp = &op2; | ||
| } | ||
| assert(hwIntrinsic != NI_Illegal); | ||
|
|
||
| if (broadcastOp != nullptr) | ||
| { | ||
| *broadcastOp = gtNewSimdCreateBroadcastNode(simdType, *broadcastOp, baseType, simdSize, | ||
| /* isSimdAsHWIntrinsic */ true); | ||
| } | ||
|
|
||
| switch (baseType) | ||
| { | ||
| case TYP_SHORT: | ||
| case TYP_USHORT: | ||
| case TYP_INT: | ||
| case TYP_UINT: | ||
| { | ||
| hwIntrinsic = NI_AVX2_MultiplyLow; | ||
| break; | ||
| } | ||
|
|
||
| case TYP_FLOAT: | ||
| case TYP_DOUBLE: | ||
| { | ||
| hwIntrinsic = NI_AVX_Multiply; | ||
| break; | ||
| } | ||
|
|
||
| default: | ||
| { | ||
| unreached(); | ||
| } | ||
| } | ||
|
|
||
| assert(hwIntrinsic != NI_Illegal); | ||
| return gtNewSimdAsHWIntrinsicNode(retType, op1, op2, hwIntrinsic, baseType, simdSize); | ||
| } | ||
|
|
||
| #elif defined(TARGET_ARM64) | ||
| case NI_Vector2_CreateBroadcast: | ||
| case NI_Vector3_CreateBroadcast: | ||
|
|
@@ -970,6 +1068,83 @@ GenTree* Compiler::impSimdAsHWIntrinsicSpecial(NamedIntrinsic intrinsic, | |
| // result = ConditionalSelect(op1, op1Dup, op2Dup) | ||
| return impSimdAsHWIntrinsicCndSel(clsHnd, retType, baseType, simdSize, op1, op1Dup, op2Dup); | ||
| } | ||
|
|
||
| case NI_VectorT128_op_Multiply: | ||
| { | ||
| NamedIntrinsic hwIntrinsic = NI_Illegal; | ||
| NamedIntrinsic scalarIntrinsic = NI_Illegal; | ||
| GenTree** scalarOp = nullptr; | ||
|
|
||
| if (varTypeIsArithmetic(op1->TypeGet())) | ||
| { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For Arm64 you don't neet broadcast operation when either
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also don't think you need to broadcast an integer value to the whole SIMD register. In this case, you can share some of the implementation for floating-point and integer types.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should need a
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Notably, |
||
| // MultiplyByScalar requires the scalar op to be op2 | ||
| std::swap(op1, op2); | ||
|
|
||
| scalarOp = &op2; | ||
| } | ||
| else if (varTypeIsArithmetic(op2->TypeGet())) | ||
| { | ||
| scalarOp = &op2; | ||
| } | ||
|
|
||
| switch (baseType) | ||
| { | ||
| case TYP_BYTE: | ||
| case TYP_UBYTE: | ||
| { | ||
| if (scalarOp != nullptr) | ||
| { | ||
| *scalarOp = gtNewSimdCreateBroadcastNode(simdType, *scalarOp, baseType, simdSize, | ||
| /* isSimdAsHWIntrinsic */ true); | ||
| } | ||
|
|
||
| hwIntrinsic = NI_AdvSimd_Multiply; | ||
| break; | ||
| } | ||
|
|
||
| case TYP_SHORT: | ||
| case TYP_USHORT: | ||
| case TYP_INT: | ||
| case TYP_UINT: | ||
| case TYP_FLOAT: | ||
| { | ||
| if (scalarOp != nullptr) | ||
| { | ||
| hwIntrinsic = NI_AdvSimd_MultiplyByScalar; | ||
| *scalarOp = gtNewSimdAsHWIntrinsicNode(TYP_SIMD8, *scalarOp, | ||
| NI_Vector64_CreateScalarUnsafe, baseType, 8); | ||
| } | ||
| else | ||
| { | ||
| hwIntrinsic = NI_AdvSimd_Multiply; | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| case TYP_DOUBLE: | ||
| { | ||
| if (scalarOp != nullptr) | ||
| { | ||
| hwIntrinsic = NI_AdvSimd_Arm64_MultiplyByScalar; | ||
| *scalarOp = | ||
| gtNewSimdAsHWIntrinsicNode(TYP_SIMD8, *scalarOp, NI_Vector64_Create, baseType, 8); | ||
| } | ||
| else | ||
| { | ||
| hwIntrinsic = NI_AdvSimd_Arm64_Multiply; | ||
| } | ||
| break; | ||
| } | ||
|
|
||
| default: | ||
| { | ||
| unreached(); | ||
| } | ||
| } | ||
|
|
||
| assert(hwIntrinsic != NI_Illegal); | ||
| return gtNewSimdAsHWIntrinsicNode(retType, op1, op2, hwIntrinsic, baseType, simdSize); | ||
| } | ||
| #else | ||
| #error Unsupported platform | ||
| #endif // !TARGET_XARCH && !TARGET_ARM64 | ||
|
|
||
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.
Shouildn't this be guarded with
compOpportunisticallyDependsOn(InstructionSet_AVX2)?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.
No, its already asserted at the top of the method (https://github.com/dotnet/runtime/blob/main/src/coreclr/jit/simdashwintrinsic.cpp#L392) as well as properly guarded further up in the stack.
We should never encounter
SimdAsHWIntrinsicClassId::VectorT256if AVX2 is not supported.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.
Why at least AVX2 is required for
VectorT256? Shouldn't AVX be sufficient?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.
Vector<T>supports all 10 primitive types and can only properly accelerate the integer types with AVX2 so it was decided it is only 32-bytes if AVX2 is supported and is 16-bytes otherwise, for the best overall perf/experience.