Skip to content
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

ARM64 - Optimizing a % b operations #65535

Merged
merged 35 commits into from
Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
64f7042
Initial work for ARM64 mod optimization
TIHan Feb 17, 2022
d1dce26
Updated comment
TIHan Feb 18, 2022
a3fbe54
Updated comment
TIHan Feb 18, 2022
729057d
Updated comment
TIHan Feb 18, 2022
c2eee76
Fixing build
TIHan Feb 18, 2022
ca333e0
Remove uneeded var
TIHan Feb 18, 2022
7fc88ed
Use '%' morph logic for both x64/arm64
TIHan Feb 23, 2022
4720f55
Merge remote-tracking branch 'upstream/main' into arm64-opt-mod
TIHan Feb 23, 2022
6656763
Adding back in divisor check for x64
TIHan Feb 24, 2022
cfa9805
Formatting
TIHan Feb 25, 2022
e058553
Update comments
TIHan Feb 25, 2022
b950ab4
Update comments
TIHan Feb 25, 2022
35c68d5
Merge remote-tracking branch 'upstream/main' into arm64-opt-mod
TIHan Feb 25, 2022
cdb7781
Merge remote-tracking branch 'upstream/main' into arm64-opt-mod
TIHan Mar 1, 2022
bcab461
Fixing
TIHan Mar 1, 2022
ba5b0ee
Updated comment
TIHan Mar 1, 2022
76af344
Updated comment
TIHan Mar 1, 2022
c7f6cb9
Tweaking x64 transformation logic for the mod opt
TIHan Mar 2, 2022
dee80b5
Tweaking x64 transformation logic for the mod opt
TIHan Mar 2, 2022
1fac071
Using IntCon
TIHan Mar 3, 2022
372bcf3
Fixed build
TIHan Mar 3, 2022
8809058
Minor tweak
TIHan Mar 3, 2022
b16d381
Fixing x64 diffs
TIHan Mar 3, 2022
9235f92
Removing flag set
TIHan Mar 3, 2022
03b2cf0
Merge remote-tracking branch 'upstream/main' into arm64-opt-mod
TIHan Mar 9, 2022
36b1e6a
Feedback
TIHan Mar 10, 2022
964b426
Fixing build
TIHan Mar 10, 2022
ec8246a
Feedback
TIHan Mar 10, 2022
27c3894
Fixing tests
TIHan Mar 11, 2022
4c36be7
Fixing tests
TIHan Mar 11, 2022
19f16b0
Fixing tests
TIHan Mar 11, 2022
f8921b9
Formatting
TIHan Mar 12, 2022
ade331d
Fixing tests
TIHan Mar 12, 2022
06d1124
Feedback
TIHan Mar 13, 2022
f142ca2
Fixing build
TIHan Mar 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -6469,6 +6469,7 @@ class Compiler
GenTree* fgPropagateCommaThrow(GenTree* parent, GenTreeOp* commaThrow, GenTreeFlags precedingSideEffects);
GenTree* fgMorphRetInd(GenTreeUnOp* tree);
GenTree* fgMorphModToSubMulDiv(GenTreeOp* tree);
GenTree* fgMorphUModToAndSub(GenTreeOp* tree);
GenTree* fgMorphSmpOpOptional(GenTreeOp* tree);
GenTree* fgMorphMultiOp(GenTreeMultiOp* multiOp);
GenTree* fgMorphConst(GenTree* tree);
Expand Down
47 changes: 47 additions & 0 deletions src/coreclr/jit/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -2110,6 +2110,10 @@ struct GenTree

inline bool IsIntegralConst() const;

inline bool IsIntegralConstUnsignedPow2() const;

inline bool IsIntegralConstAbsPow2() const;

inline bool IsIntCnsFitsInI32(); // Constant fits in INT32

inline bool IsCnsFltOrDbl() const;
Expand Down Expand Up @@ -8343,6 +8347,49 @@ inline bool GenTree::IsIntegralConst() const
#endif // !TARGET_64BIT
}

//-------------------------------------------------------------------------
// IsIntegralConstUnsignedPow2: Determines whether the unsigned value of
// an integral constant is the power of 2.
//
// Return Value:
// Returns true if the unsigned value of a GenTree's integral constant
// is the power of 2.
//
// Notes:
// Integral constant nodes store its value in signed form.
// This should handle cases where an unsigned-int was logically used in
// user code.
//
inline bool GenTree::IsIntegralConstUnsignedPow2() const
{
if (IsIntegralConst())
{
return isPow2((UINT64)AsIntConCommon()->IntegralValue());
}

return false;
}

//-------------------------------------------------------------------------
// IsIntegralConstAbsPow2: Determines whether the absolute value of
// an integral constant is the power of 2.
//
// Return Value:
// Returns true if the absolute value of a GenTree's integral constant
// is the power of 2.
//
inline bool GenTree::IsIntegralConstAbsPow2() const
{
if (IsIntegralConst())
{
INT64 svalue = AsIntConCommon()->IntegralValue();
size_t value = (svalue == SSIZE_T_MIN) ? static_cast<size_t>(svalue) : static_cast<size_t>(abs(svalue));
return isPow2(value);
}

return false;
}

// Is this node an integer constant that fits in a 32-bit signed integer (INT32)
inline bool GenTree::IsIntCnsFitsInI32()
{
Expand Down
92 changes: 51 additions & 41 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11396,58 +11396,34 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac)
#endif
#endif // !TARGET_64BIT

#ifdef TARGET_ARM64
// For ARM64 we don't have a remainder instruction,
// The architecture manual suggests the following transformation to
// generate code for such operator:
//
// a % b = a - (a / b) * b;
//
// TODO: there are special cases where it can be done better, for example
// when the modulo operation is unsigned and the divisor is a
// integer constant power of two. In this case, we can make the transform:
//
// a % b = a & (b - 1);
//
// Lower supports it for all cases except when `a` is constant, but
// in Morph we can't guarantee that `a` won't be transformed into a constant,
// so can't guarantee that lower will be able to do this optimization.
if (!optValnumCSE_phase)
{
// Do "a % b = a - (a / b) * b" morph always, see TODO before this block.
bool doMorphModToSubMulDiv = true;

if (doMorphModToSubMulDiv)
#ifdef TARGET_ARM64
if (tree->OperIs(GT_UMOD) && op2->IsIntegralConstUnsignedPow2())
{
assert(!optValnumCSE_phase);

tree = fgMorphModToSubMulDiv(tree->AsOp());
// Transformation: a % b = a & (b - 1);
tree = fgMorphUModToAndSub(tree->AsOp());
op1 = tree->AsOp()->gtOp1;
op2 = tree->AsOp()->gtOp2;
}
}
#else // !TARGET_ARM64
// If b is not a power of 2 constant then lowering replaces a % b
// with a - (a / b) * b and applies magic division optimization to
// a / b. The code may already contain an a / b expression (e.g.
// x = a / 10; y = a % 10;) and then we end up with redundant code.
// If we convert % to / here we give CSE the opportunity to eliminate
// the redundant division. If there's no redundant division then
// nothing is lost, lowering would have done this transform anyway.

if (!optValnumCSE_phase && ((tree->OperGet() == GT_MOD) && op2->IsIntegralConst()))
{
ssize_t divisorValue = op2->AsIntCon()->IconValue();
size_t absDivisorValue = (divisorValue == SSIZE_T_MIN) ? static_cast<size_t>(divisorValue)
: static_cast<size_t>(abs(divisorValue));

if (!isPow2(absDivisorValue))
// ARM64 architecture manual suggests this transformation
// for the mod operator.
else
#else
// XARCH only applies this transformation if we know
// that magic division will be used - which is determined
// when 'b' is not a power of 2 constant and mod operator is signed.
// Lowering for XARCH does this optimization already,
// but is also done here to take advantage of CSE.
if (tree->OperIs(GT_MOD) && op2->IsIntegralConst() && !op2->IsIntegralConstAbsPow2())
#endif
{
// Transformation: a % b = a - (a / b) * b;
tree = fgMorphModToSubMulDiv(tree->AsOp());
op1 = tree->AsOp()->gtOp1;
op2 = tree->AsOp()->gtOp2;
}
}
#endif // !TARGET_ARM64
break;

USE_HELPER_FOR_ARITH:
Expand Down Expand Up @@ -14738,6 +14714,40 @@ GenTree* Compiler::fgMorphModToSubMulDiv(GenTreeOp* tree)
return sub;
}

//------------------------------------------------------------------------
// fgMorphUModToAndSub: Transform a % b into the equivalent a & (b - 1).
// '%' must be unsigned (GT_UMOD).
// 'a' and 'b' must be integers.
// 'b' must be a constant and a power of two.
//
// Arguments:
// tree - The GT_UMOD tree to morph
//
// Returns:
// The morphed tree
//
// Notes:
// This is more optimized than calling fgMorphModToSubMulDiv.
//
GenTree* Compiler::fgMorphUModToAndSub(GenTreeOp* tree)
{
JITDUMP("\nMorphing UMOD [%06u] to And/Sub\n", dspTreeID(tree));

assert(tree->OperIs(GT_UMOD));
assert(tree->gtOp2->IsIntegralConstAbsPow2());

var_types type = tree->TypeGet();

GenTree* const sub = gtNewOperNode(GT_SUB, type, tree->gtOp2, gtNewOneConNode(type));
TIHan marked this conversation as resolved.
Show resolved Hide resolved
GenTree* const andSub = gtNewOperNode(GT_AND, type, tree->gtOp1, sub);

INDEBUG(andSub->gtDebugFlags |= GTF_DEBUG_NODE_MORPHED);

DEBUG_DESTROY_NODE(tree);

return andSub;
}

//------------------------------------------------------------------------------
// fgOperIsBitwiseRotationRoot : Check if the operation can be a root of a bitwise rotation tree.
//
Expand Down