Skip to content

Commit

Permalink
clearing bit utils (#2301)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #2301

n_least_significant_bits
n_most_significant_bits

clear_n_least_significant_bits
set_n_least_significant_bits
clear_n_most_significant_bits
set_n_most_significant_bits

Simple utils that correctly handle corner cases, such as shift == 64.
I looked at the assembly a bit, probably that's ok.
For x86 I used bmi2 where was appropriate.

Reviewed By: Gownta

Differential Revision: D63329499

fbshipit-source-id: 920d3fe597c63e8ef6e0319ba81c8fc5f3aa5c0e
  • Loading branch information
DenisYaroshevskiy authored and facebook-github-bot committed Oct 1, 2024
1 parent 6a64ce6 commit c669175
Show file tree
Hide file tree
Showing 2 changed files with 375 additions and 0 deletions.
151 changes: 151 additions & 0 deletions folly/lang/Bits.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@
#include <folly/lang/CString.h>
#include <folly/portability/Builtins.h>

#ifdef __BMI2__
#include <immintrin.h>
#endif

#if __has_include(<bit>) && (__cplusplus >= 202002L || (defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L))
#include <bit>
#endif
Expand Down Expand Up @@ -106,6 +110,11 @@ constexpr std::make_unsigned_t<Dst> bits_to_unsigned(Src const s) {
static_assert(std::is_unsigned<Dst>::value, "signed type");
return static_cast<Dst>(to_unsigned(s));
}

template <typename T>
inline constexpr bool supported_in_bits_operations_v =
std::is_unsigned_v<T> && sizeof(T) <= 8;

} // namespace detail

/// findFirstSet
Expand Down Expand Up @@ -223,6 +232,148 @@ inline constexpr T strictPrevPowTwo(T const v) {
return v > 1 ? prevPowTwo(T(v - 1)) : T(0);
}

/// n_least_significant_bits
/// n_least_significant_bits_fn
///
/// Returns an unsigned integer of type T, where n
/// least significant (right) bits are set and others are not.
template <class T>
struct n_least_significant_bits_fn {
static_assert(detail::supported_in_bits_operations_v<T>, "");

FOLLY_NODISCARD constexpr T operator()(std::uint32_t n) const {
if (!folly::is_constant_evaluated_or(true)) {
compiler_may_unsafely_assume(n <= sizeof(T) * 8);

#ifdef __BMI2__
if constexpr (sizeof(T) <= 4) {
return static_cast<T>(_bzhi_u32(static_cast<std::uint32_t>(-1), n));
}
return static_cast<T>(_bzhi_u64(static_cast<std::uint64_t>(-1), n));
#endif
}

if (sizeof(T) == 8 && n == 64) {
return static_cast<T>(-1);
}
return static_cast<T>((std::uint64_t{1} << n) - 1);
}
};

template <class T>
inline constexpr n_least_significant_bits_fn<T> n_least_significant_bits;

/// n_most_significant_bits
/// n_most_significant_bits_fn
///
/// Returns an unsigned integer of type T, where n
/// most significant bits (left) are set and others are not.
template <class T>
struct n_most_significant_bits_fn {
static_assert(detail::supported_in_bits_operations_v<T>, "");

FOLLY_NODISCARD constexpr T operator()(std::uint32_t n) const {
if (!folly::is_constant_evaluated_or(true)) {
compiler_may_unsafely_assume(n <= sizeof(T) * 8);

#ifdef __BMI2__
// assembler looks smaller here, if we use bzhi from `set_lowest_n_bits`
if constexpr (sizeof(T) == 8) {
return static_cast<T>(~n_least_significant_bits<T>(64 - n));
}
#endif
}

if (sizeof(T) == 8 && n == 0) {
return 0;
}
n = sizeof(T) * 8 - n;

std::uint64_t ones = static_cast<T>(~0);
return static_cast<T>(ones << n);
}
};

template <class T>
inline constexpr n_most_significant_bits_fn<T> n_most_significant_bits;

/// clear_n_least_significant_bits
/// clear_n_least_significant_bits_fn
///
/// Clears n least significant (right) bits. Other bits stay the same.
struct clear_n_least_significant_bits_fn {
template <typename T>
FOLLY_NODISCARD constexpr T operator()(T x, std::uint32_t n) const {
static_assert(detail::supported_in_bits_operations_v<T>, "");

// alternative is to do two shifts but that has
// a dependency between them, so is likely worse
return x & n_most_significant_bits<T>(sizeof(T) * 8 - n);
}
};

inline constexpr clear_n_least_significant_bits_fn
clear_n_least_significant_bits;

/// set_n_least_significant_bits
/// set_n_least_significant_bits_fn
///
/// Sets n least significant (right) bits. Other bits stay the same.
struct set_n_least_significant_bits_fn {
template <typename T>
FOLLY_NODISCARD constexpr T operator()(T x, std::uint32_t n) const {
static_assert(detail::supported_in_bits_operations_v<T>, "");

// alternative is to do two shifts but that has
// a dependency between them, so is likely worse
return x | n_least_significant_bits<T>(n);
}
};

inline constexpr set_n_least_significant_bits_fn set_n_least_significant_bits;

/// clear_n_most_significant_bits
/// clear_n_most_significant_bits_fn
///
/// Clears n most significant (left) bits. Other bits stay the same.
struct clear_n_most_significant_bits_fn {
template <typename T>
FOLLY_NODISCARD constexpr T operator()(T x, std::uint32_t n) const {
static_assert(detail::supported_in_bits_operations_v<T>, "");

if (!folly::is_constant_evaluated_or(true)) {
compiler_may_unsafely_assume(n <= sizeof(T) * 8);

#ifdef __BMI2__
if constexpr (sizeof(T) <= 4) {
return static_cast<T>(_bzhi_u32(x, sizeof(T) * 8 - n));
}
return static_cast<T>(_bzhi_u64(x, sizeof(T) * 8 - n));
#endif
}

// alternative is to do two shifts but that has
// a dependency between them, so is likely worse
return x & n_least_significant_bits<T>(sizeof(T) * 8 - n);
}
};

inline constexpr clear_n_most_significant_bits_fn clear_n_most_significant_bits;

/// set_n_most_significant_bits
/// set_n_most_significant_bits_fn
///
/// Sets n most significant (left) bits. Other bits stay the same.
struct set_n_most_significant_bits_fn {
template <typename T>
FOLLY_NODISCARD constexpr T operator()(T x, std::uint32_t n) const {
static_assert(detail::supported_in_bits_operations_v<T>, "");
return x | n_most_significant_bits<T>(n);
}
};

inline constexpr set_n_most_significant_bits_fn set_n_most_significant_bits;

/**
* Endianness detection and manipulation primitives.
*/
Expand Down
Loading

0 comments on commit c669175

Please sign in to comment.