Skip to content

Commit

Permalink
MathExtras: avoid unnecessarily widening types (#95426)
Browse files Browse the repository at this point in the history
Several multi-argument functions unnecessarily widen types beyond the
argument types. Template'ize the functions, and use std::common_type_t
to avoid this, hence optimizing the functions. A requirement of this
patch is to change the overflow behavior of alignTo to only overflow
when the result isn't representable in the return type.
  • Loading branch information
artagnon committed Jun 29, 2024
1 parent 5cc1287 commit 5627794
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 69 deletions.
189 changes: 122 additions & 67 deletions llvm/include/llvm/Support/MathExtras.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@
#include <type_traits>

namespace llvm {
/// Some template parameter helpers to optimize for bitwidth, for functions that
/// take multiple arguments.

// We can't verify signedness, since callers rely on implicit coercions to
// signed/unsigned.
template <typename T, typename U>
using enableif_int =
std::enable_if_t<std::is_integral_v<T> && std::is_integral_v<U>>;

// Use std::common_type_t to widen only up to the widest argument.
template <typename T, typename U, typename = enableif_int<T, U>>
using common_uint =
std::common_type_t<std::make_unsigned_t<T>, std::make_unsigned_t<U>>;
template <typename T, typename U, typename = enableif_int<T, U>>
using common_sint =
std::common_type_t<std::make_signed_t<T>, std::make_signed_t<U>>;

/// Mathematical constants.
namespace numbers {
Expand Down Expand Up @@ -346,7 +362,8 @@ inline unsigned Log2_64_Ceil(uint64_t Value) {

/// A and B are either alignments or offsets. Return the minimum alignment that
/// may be assumed after adding the two together.
constexpr uint64_t MinAlign(uint64_t A, uint64_t B) {
template <typename U, typename V, typename T = common_uint<U, V>>
constexpr T MinAlign(U A, V B) {
// The largest power of 2 that divides both A and B.
//
// Replace "-Value" by "1+~Value" in the following commented code to avoid
Expand All @@ -355,6 +372,11 @@ constexpr uint64_t MinAlign(uint64_t A, uint64_t B) {
return (A | B) & (1 + ~(A | B));
}

/// Fallback when arguments aren't integral.
constexpr uint64_t MinAlign(uint64_t A, uint64_t B) {
return (A | B) & (1 + ~(A | B));
}

/// Returns the next power of two (in 64-bits) that is strictly greater than A.
/// Returns zero on overflow.
constexpr uint64_t NextPowerOf2(uint64_t A) {
Expand All @@ -375,60 +397,17 @@ inline uint64_t PowerOf2Ceil(uint64_t A) {
return UINT64_C(1) << Log2_64_Ceil(A);
}

/// Returns the next integer (mod 2**64) that is greater than or equal to
/// \p Value and is a multiple of \p Align. \p Align must be non-zero.
///
/// Examples:
/// \code
/// alignTo(5, 8) = 8
/// alignTo(17, 8) = 24
/// alignTo(~0LL, 8) = 0
/// alignTo(321, 255) = 510
/// \endcode
///
/// May overflow.
inline uint64_t alignTo(uint64_t Value, uint64_t Align) {
assert(Align != 0u && "Align can't be 0.");
return (Value + Align - 1) / Align * Align;
}

inline uint64_t alignToPowerOf2(uint64_t Value, uint64_t Align) {
assert(Align != 0 && (Align & (Align - 1)) == 0 &&
"Align must be a power of 2");
// Replace unary minus to avoid compilation error on Windows:
// "unary minus operator applied to unsigned type, result still unsigned"
uint64_t negAlign = (~Align) + 1;
return (Value + Align - 1) & negAlign;
}

/// If non-zero \p Skew is specified, the return value will be a minimal integer
/// that is greater than or equal to \p Size and equal to \p A * N + \p Skew for
/// some integer N. If \p Skew is larger than \p A, its value is adjusted to '\p
/// Skew mod \p A'. \p Align must be non-zero.
///
/// Examples:
/// \code
/// alignTo(5, 8, 7) = 7
/// alignTo(17, 8, 1) = 17
/// alignTo(~0LL, 8, 3) = 3
/// alignTo(321, 255, 42) = 552
/// \endcode
inline uint64_t alignTo(uint64_t Value, uint64_t Align, uint64_t Skew) {
assert(Align != 0u && "Align can't be 0.");
Skew %= Align;
return alignTo(Value - Skew, Align) + Skew;
}

/// Returns the next integer (mod 2**64) that is greater than or equal to
/// \p Value and is a multiple of \c Align. \c Align must be non-zero.
template <uint64_t Align> constexpr uint64_t alignTo(uint64_t Value) {
static_assert(Align != 0u, "Align must be non-zero");
return (Value + Align - 1) / Align * Align;
}

/// Returns the integer ceil(Numerator / Denominator). Unsigned version.
/// Guaranteed to never overflow.
inline uint64_t divideCeil(uint64_t Numerator, uint64_t Denominator) {
template <typename U, typename V, typename T = common_uint<U, V>>
constexpr T divideCeil(U Numerator, V Denominator) {
assert(Denominator && "Division by zero");
T Bias = (Numerator != 0);
return (Numerator - Bias) / Denominator + Bias;
}

/// Fallback when arguments aren't integral.
constexpr uint64_t divideCeil(uint64_t Numerator, uint64_t Denominator) {
assert(Denominator && "Division by zero");
uint64_t Bias = (Numerator != 0);
return (Numerator - Bias) / Denominator + Bias;
Expand All @@ -437,12 +416,13 @@ inline uint64_t divideCeil(uint64_t Numerator, uint64_t Denominator) {
/// Returns the integer ceil(Numerator / Denominator). Signed version.
/// Guaranteed to never overflow, unless Numerator is INT64_MIN and Denominator
/// is -1.
inline int64_t divideCeilSigned(int64_t Numerator, int64_t Denominator) {
template <typename U, typename V, typename T = common_sint<U, V>>
constexpr T divideCeilSigned(U Numerator, V Denominator) {
assert(Denominator && "Division by zero");
if (!Numerator)
return 0;
// C's integer division rounds towards 0.
int64_t Bias = (Denominator >= 0 ? 1 : -1);
T Bias = Denominator >= 0 ? 1 : -1;
bool SameSign = (Numerator >= 0) == (Denominator >= 0);
return SameSign ? (Numerator - Bias) / Denominator + 1
: Numerator / Denominator;
Expand All @@ -451,36 +431,111 @@ inline int64_t divideCeilSigned(int64_t Numerator, int64_t Denominator) {
/// Returns the integer floor(Numerator / Denominator). Signed version.
/// Guaranteed to never overflow, unless Numerator is INT64_MIN and Denominator
/// is -1.
inline int64_t divideFloorSigned(int64_t Numerator, int64_t Denominator) {
template <typename U, typename V, typename T = common_sint<U, V>>
constexpr T divideFloorSigned(U Numerator, V Denominator) {
assert(Denominator && "Division by zero");
if (!Numerator)
return 0;
// C's integer division rounds towards 0.
int64_t Bias = Denominator >= 0 ? -1 : 1;
T Bias = Denominator >= 0 ? -1 : 1;
bool SameSign = (Numerator >= 0) == (Denominator >= 0);
return SameSign ? Numerator / Denominator
: (Numerator - Bias) / Denominator - 1;
}

/// Returns the remainder of the Euclidean division of LHS by RHS. Result is
/// always non-negative.
inline int64_t mod(int64_t Numerator, int64_t Denominator) {
template <typename U, typename V, typename T = common_sint<U, V>>
constexpr T mod(U Numerator, V Denominator) {
assert(Denominator >= 1 && "Mod by non-positive number");
int64_t Mod = Numerator % Denominator;
T Mod = Numerator % Denominator;
return Mod < 0 ? Mod + Denominator : Mod;
}

/// Returns (Numerator / Denominator) rounded by round-half-up. Guaranteed to
/// never overflow.
inline uint64_t divideNearest(uint64_t Numerator, uint64_t Denominator) {
template <typename U, typename V, typename T = common_uint<U, V>>
constexpr T divideNearest(U Numerator, V Denominator) {
assert(Denominator && "Division by zero");
uint64_t Mod = Numerator % Denominator;
return (Numerator / Denominator) + (Mod > (Denominator - 1) / 2);
T Mod = Numerator % Denominator;
return (Numerator / Denominator) +
(Mod > (static_cast<T>(Denominator) - 1) / 2);
}

/// Returns the next integer (mod 2**nbits) that is greater than or equal to
/// \p Value and is a multiple of \p Align. \p Align must be non-zero.
///
/// Examples:
/// \code
/// alignTo(5, 8) = 8
/// alignTo(17, 8) = 24
/// alignTo(~0LL, 8) = 0
/// alignTo(321, 255) = 510
/// \endcode
///
/// Will overflow only if result is not representable in T.
template <typename U, typename V, typename T = common_uint<U, V>>
constexpr T alignTo(U Value, V Align) {
assert(Align != 0u && "Align can't be 0.");
T CeilDiv = divideCeil(Value, Align);
return CeilDiv * Align;
}

/// Fallback when arguments aren't integral.
constexpr uint64_t alignTo(uint64_t Value, uint64_t Align) {
assert(Align != 0u && "Align can't be 0.");
uint64_t CeilDiv = divideCeil(Value, Align);
return CeilDiv * Align;
}

constexpr uint64_t alignToPowerOf2(uint64_t Value, uint64_t Align) {
assert(Align != 0 && (Align & (Align - 1)) == 0 &&
"Align must be a power of 2");
// Replace unary minus to avoid compilation error on Windows:
// "unary minus operator applied to unsigned type, result still unsigned"
uint64_t NegAlign = (~Align) + 1;
return (Value + Align - 1) & NegAlign;
}

/// If non-zero \p Skew is specified, the return value will be a minimal integer
/// that is greater than or equal to \p Size and equal to \p A * N + \p Skew for
/// some integer N. If \p Skew is larger than \p A, its value is adjusted to '\p
/// Skew mod \p A'. \p Align must be non-zero.
///
/// Examples:
/// \code
/// alignTo(5, 8, 7) = 7
/// alignTo(17, 8, 1) = 17
/// alignTo(~0LL, 8, 3) = 3
/// alignTo(321, 255, 42) = 552
/// \endcode
///
/// May overflow.
template <typename U, typename V, typename W,
typename T = common_uint<common_uint<U, V>, W>>
constexpr T alignTo(U Value, V Align, W Skew) {
assert(Align != 0u && "Align can't be 0.");
Skew %= Align;
return alignTo(Value - Skew, Align) + Skew;
}

/// Returns the largest uint64_t less than or equal to \p Value and is
/// \p Skew mod \p Align. \p Align must be non-zero
inline uint64_t alignDown(uint64_t Value, uint64_t Align, uint64_t Skew = 0) {
/// Returns the next integer (mod 2**nbits) that is greater than or equal to
/// \p Value and is a multiple of \c Align. \c Align must be non-zero.
///
/// Will overflow only if result is not representable in T.
template <auto Align, typename V, typename T = common_uint<decltype(Align), V>>
constexpr T alignTo(V Value) {
static_assert(Align != 0u, "Align must be non-zero");
T CeilDiv = divideCeil(Value, Align);
return CeilDiv * Align;
}

/// Returns the largest unsigned integer less than or equal to \p Value and is
/// \p Skew mod \p Align. \p Align must be non-zero. Guaranteed to never
/// overflow.
template <typename U, typename V, typename W = uint8_t,
typename T = common_uint<common_uint<U, V>, W>>
constexpr T alignDown(U Value, V Align, W Skew = 0) {
assert(Align != 0u && "Align can't be 0.");
Skew %= Align;
return (Value - Skew) / Align * Align + Skew;
Expand Down Expand Up @@ -524,8 +579,8 @@ inline int64_t SignExtend64(uint64_t X, unsigned B) {

/// Subtract two unsigned integers, X and Y, of type T and return the absolute
/// value of the result.
template <typename T>
std::enable_if_t<std::is_unsigned_v<T>, T> AbsoluteDifference(T X, T Y) {
template <typename U, typename V, typename T = common_uint<U, V>>
constexpr T AbsoluteDifference(U X, V Y) {
return X > Y ? (X - Y) : (Y - X);
}

Expand Down
18 changes: 16 additions & 2 deletions llvm/unittests/Support/MathExtrasTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,21 +189,35 @@ TEST(MathExtras, AlignTo) {
EXPECT_EQ(8u, alignTo(5, 8));
EXPECT_EQ(24u, alignTo(17, 8));
EXPECT_EQ(0u, alignTo(~0LL, 8));
EXPECT_EQ(static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) + 1,
alignTo(std::numeric_limits<uint32_t>::max(), 2));
EXPECT_EQ(8u, alignTo(5ULL, 8ULL));

EXPECT_EQ(8u, alignTo<8>(5));
EXPECT_EQ(24u, alignTo<8>(17));
EXPECT_EQ(0u, alignTo<8>(~0LL));
EXPECT_EQ(254u,
alignTo<static_cast<uint8_t>(127)>(static_cast<uint8_t>(200)));

EXPECT_EQ(7u, alignTo(5, 8, 7));
EXPECT_EQ(17u, alignTo(17, 8, 1));
EXPECT_EQ(3u, alignTo(~0LL, 8, 3));
EXPECT_EQ(552u, alignTo(321, 255, 42));
EXPECT_EQ(std::numeric_limits<uint32_t>::max(),
alignTo(std::numeric_limits<uint32_t>::max(), 2, 1));

// Overflow.
EXPECT_EQ(0u, alignTo(static_cast<uint8_t>(200), static_cast<uint8_t>(128)));
EXPECT_EQ(0u, alignTo<static_cast<uint8_t>(128)>(static_cast<uint8_t>(200)));
EXPECT_EQ(0u, alignTo(static_cast<uint8_t>(200), static_cast<uint8_t>(128),
static_cast<uint8_t>(0)));
EXPECT_EQ(0u, alignTo(std::numeric_limits<uint32_t>::max(), 2));
}

TEST(MathExtras, AlignToPowerOf2) {
EXPECT_EQ(0u, alignToPowerOf2(0u, 8));
EXPECT_EQ(8u, alignToPowerOf2(5, 8));
EXPECT_EQ(24u, alignToPowerOf2(17, 8));
EXPECT_EQ(0u, alignToPowerOf2(~0LL, 8));
EXPECT_EQ(240u, alignToPowerOf2(240, 16));
EXPECT_EQ(static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) + 1,
alignToPowerOf2(std::numeric_limits<uint32_t>::max(), 2));
}
Expand Down

0 comments on commit 5627794

Please sign in to comment.