Add portable bit field implementation
Summary: @public Our usage of C++ bit fields has lead to quite some problems with different compiler setups. Problems include sign bits, alignment, etc. Here we introduce a portable implementation as a variadic template, allowing the user to store a number of booleans and enums (defined with `YG_ENUM_SEQ_DECL`) in an unsigned integer type of their choice. This will replace all usages of bit fields across the Yoga code base. Differential Revision: D16647801 fbshipit-source-id: 230ffab500885a3ad662ea8f19e35a5e9357a563
This commit is contained in:
committed by
Facebook Github Bot
parent
947230958d
commit
dadf0473b7
243
tests/BitfieldTest.cpp
Normal file
243
tests/BitfieldTest.cpp
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <yoga/Bitfield.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace yoga {
|
||||
|
||||
TEST(Bitfield, one_boolean_defaults_to_false) {
|
||||
constexpr auto bf = Bitfield<uint8_t, bool>{};
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), false);
|
||||
static_assert(
|
||||
bf.at<0>() == false, "first boolean member must default to false");
|
||||
}
|
||||
|
||||
TEST(Bitfield, one_boolean_can_be_initialized_to_true) {
|
||||
constexpr auto bf = Bitfield<uint8_t, bool>{true};
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), true);
|
||||
static_assert(
|
||||
bf.at<0>() == true, "first boolean member must be initialized to true");
|
||||
}
|
||||
|
||||
TEST(Bitfield, one_boolean_can_be_set_to_true) {
|
||||
auto bf = Bitfield<uint8_t, bool>{};
|
||||
|
||||
bf.at<0>() = true;
|
||||
ASSERT_EQ(bf.at<0>(), true);
|
||||
}
|
||||
|
||||
TEST(Bitfield, second_boolean_defaults_to_false) {
|
||||
constexpr auto bf = Bitfield<uint8_t, bool, bool>{};
|
||||
|
||||
ASSERT_EQ(bf.at<1>(), false);
|
||||
static_assert(
|
||||
bf.at<1>() == false, "second boolean member must default to false");
|
||||
}
|
||||
|
||||
TEST(Bitfield, second_boolean_can_be_initialized_to_true) {
|
||||
constexpr auto bf = Bitfield<uint8_t, bool, bool>{false, true};
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), false);
|
||||
ASSERT_EQ(bf.at<1>(), true);
|
||||
static_assert(
|
||||
bf.at<0>() == false, "first boolean member must default to false");
|
||||
static_assert(
|
||||
bf.at<1>() == true, "second boolean member must be initialized to true");
|
||||
}
|
||||
|
||||
TEST(Bitfield, second_boolean_can_be_set_to_true) {
|
||||
auto bf = Bitfield<uint8_t, bool, bool>{};
|
||||
|
||||
bf.at<1>() = true;
|
||||
ASSERT_EQ(bf.at<0>(), false);
|
||||
ASSERT_EQ(bf.at<1>(), true);
|
||||
}
|
||||
|
||||
TEST(Bitfield, third_boolean_defaults_to_false) {
|
||||
constexpr auto bf = Bitfield<uint8_t, bool, bool, bool>{};
|
||||
|
||||
ASSERT_EQ(bf.at<2>(), false);
|
||||
static_assert(
|
||||
bf.at<2>() == false, "second boolean member must default to false");
|
||||
}
|
||||
|
||||
TEST(Bitfield, third_boolean_can_be_initialized_to_true) {
|
||||
constexpr auto bf = Bitfield<uint8_t, bool, bool, bool>{false, false, true};
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), false);
|
||||
ASSERT_EQ(bf.at<1>(), false);
|
||||
ASSERT_EQ(bf.at<2>(), true);
|
||||
static_assert(
|
||||
bf.at<0>() == false, "first boolean member must default to false");
|
||||
static_assert(
|
||||
bf.at<1>() == false, "second boolean member must default to false");
|
||||
static_assert(
|
||||
bf.at<2>() == true, "second boolean member must be initialized to true");
|
||||
}
|
||||
|
||||
TEST(Bitfield, third_boolean_can_be_set_to_true) {
|
||||
auto bf = Bitfield<uint8_t, bool, bool, bool>{};
|
||||
|
||||
bf.at<2>() = true;
|
||||
ASSERT_EQ(bf.at<0>(), false);
|
||||
ASSERT_EQ(bf.at<1>(), false);
|
||||
ASSERT_EQ(bf.at<2>(), true);
|
||||
}
|
||||
|
||||
TEST(Bitfield, initializing_boolean_values_does_not_spill_over) {
|
||||
constexpr auto bf =
|
||||
Bitfield<uint8_t, bool, bool, bool>{false, (bool) 7, false};
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), false);
|
||||
ASSERT_EQ(bf.at<1>(), true);
|
||||
ASSERT_EQ(bf.at<2>(), false);
|
||||
static_assert(
|
||||
bf.at<0>() == false, "first boolean member must be initialized to false");
|
||||
static_assert(
|
||||
bf.at<1>() == true, "second boolean member must be initialized to true");
|
||||
static_assert(
|
||||
bf.at<2>() == false, "third boolean member must be initialized to false");
|
||||
}
|
||||
|
||||
TEST(Bitfield, setting_boolean_values_does_not_spill_over) {
|
||||
auto bf = Bitfield<uint8_t, bool, bool, bool>{};
|
||||
|
||||
bf.at<1>() = (bool) 7;
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), false);
|
||||
ASSERT_EQ(bf.at<1>(), true);
|
||||
ASSERT_EQ(bf.at<2>(), false);
|
||||
}
|
||||
|
||||
TEST(Bitfield, first_enum_defaults_to_0) {
|
||||
constexpr auto bf = Bitfield<uint8_t, YGAlign>{};
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), YGAlignAuto);
|
||||
static_assert(
|
||||
bf.at<0>() == YGAlignAuto, "first enum member must default to 0");
|
||||
}
|
||||
|
||||
TEST(Bitfield, first_enum_can_be_initialized) {
|
||||
constexpr auto bf = Bitfield<uint8_t, YGAlign>{YGAlignFlexEnd};
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), YGAlignFlexEnd);
|
||||
static_assert(
|
||||
bf.at<0>() == YGAlignFlexEnd,
|
||||
"first enum member must be initialized to YGAlignFlexEnd");
|
||||
}
|
||||
|
||||
TEST(Bitfield, first_enum_can_be_set) {
|
||||
auto bf = Bitfield<uint8_t, YGAlign>{};
|
||||
|
||||
bf.at<0>() = YGAlignSpaceBetween;
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), YGAlignSpaceBetween);
|
||||
}
|
||||
|
||||
TEST(Bitfield, second_enum_defaults_to_0) {
|
||||
constexpr auto bf = Bitfield<uint8_t, YGAlign, YGEdge>{};
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), YGAlignAuto);
|
||||
ASSERT_EQ(bf.at<1>(), YGEdgeLeft);
|
||||
static_assert(
|
||||
bf.at<0>() == YGAlignAuto, "first enum member must default to 0");
|
||||
static_assert(
|
||||
bf.at<1>() == YGEdgeLeft, "second enum member must default to 0");
|
||||
}
|
||||
|
||||
TEST(Bitfield, second_enum_can_be_initialized) {
|
||||
constexpr auto bf =
|
||||
Bitfield<uint8_t, YGAlign, YGEdge>{YGAlignAuto, YGEdgeAll};
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), YGAlignAuto);
|
||||
ASSERT_EQ(bf.at<1>(), YGEdgeAll);
|
||||
static_assert(
|
||||
bf.at<0>() == YGAlignAuto, "first enum member must default to 0");
|
||||
static_assert(
|
||||
bf.at<1>() == YGEdgeAll,
|
||||
"second enum member must be initialized to YGEdgeAll");
|
||||
}
|
||||
|
||||
TEST(Bitfield, second_enum_can_be_set) {
|
||||
auto bf = Bitfield<uint8_t, YGAlign, YGEdge>{};
|
||||
|
||||
bf.at<1>() = YGEdgeAll;
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), YGAlignAuto);
|
||||
ASSERT_EQ(bf.at<1>(), YGEdgeAll);
|
||||
}
|
||||
|
||||
TEST(Bitfield, third_enum_defaults_to_0) {
|
||||
constexpr auto bf = Bitfield<uint8_t, YGAlign, bool, YGEdge>{};
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), YGAlignAuto);
|
||||
ASSERT_EQ(bf.at<1>(), false);
|
||||
ASSERT_EQ(bf.at<2>(), YGEdgeLeft);
|
||||
static_assert(
|
||||
bf.at<0>() == YGAlignAuto, "first enum member must default to 0");
|
||||
static_assert(
|
||||
bf.at<1>() == false, "middle boolean member must default to false");
|
||||
static_assert(bf.at<2>() == YGEdgeLeft, "last enum member must default to 0");
|
||||
}
|
||||
|
||||
TEST(Bitfield, third_enum_can_be_initialized) {
|
||||
constexpr auto bf = Bitfield<uint8_t, YGAlign, bool, YGEdge>{
|
||||
YGAlignAuto, false, YGEdgeVertical};
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), YGAlignAuto);
|
||||
ASSERT_EQ(bf.at<1>(), false);
|
||||
ASSERT_EQ(bf.at<2>(), YGEdgeVertical);
|
||||
static_assert(
|
||||
bf.at<0>() == YGAlignAuto, "first enum member must default to 0");
|
||||
static_assert(
|
||||
bf.at<1>() == false, "middle boolean member must default to false");
|
||||
static_assert(
|
||||
bf.at<2>() == YGEdgeVertical,
|
||||
"second enum member must be initialized to YGEdgeVertical");
|
||||
}
|
||||
|
||||
TEST(Bitfield, third_enum_can_be_set) {
|
||||
auto bf = Bitfield<uint8_t, YGAlign, bool, YGEdge>{};
|
||||
|
||||
bf.at<2>() = YGEdgeVertical;
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), YGAlignAuto);
|
||||
ASSERT_EQ(bf.at<1>(), false);
|
||||
ASSERT_EQ(bf.at<2>(), YGEdgeVertical);
|
||||
}
|
||||
|
||||
TEST(Bitfield, initializing_values_does_not_spill_over) {
|
||||
constexpr auto bf = Bitfield<uint8_t, YGAlign, YGEdge, bool>{
|
||||
(YGAlign) 0, (YGEdge) 0xffffff, false};
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), (YGAlign) 0);
|
||||
ASSERT_EQ(bf.at<1>(), 0xf);
|
||||
ASSERT_EQ(bf.at<2>(), false);
|
||||
static_assert(bf.at<0>() == 0, "first enum member must be initialized to 0");
|
||||
static_assert(
|
||||
bf.at<1>() == 0xf, "second member must be initialized to YGEdgeVertical");
|
||||
static_assert(
|
||||
bf.at<2>() == false, "boolean member must be initialized to false");
|
||||
}
|
||||
|
||||
TEST(Bitfield, setting_values_does_not_spill_over) {
|
||||
auto bf = Bitfield<uint8_t, YGAlign, YGEdge, bool>{};
|
||||
|
||||
bf.at<1>() = (YGEdge) 0xffffff;
|
||||
|
||||
ASSERT_EQ(bf.at<0>(), 0);
|
||||
ASSERT_EQ(bf.at<1>(), 0xf);
|
||||
ASSERT_EQ(bf.at<2>(), false);
|
||||
}
|
||||
|
||||
} // namespace yoga
|
||||
} // namespace facebook
|
144
yoga/Bitfield.h
Normal file
144
yoga/Bitfield.h
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the LICENSE
|
||||
* file in the root directory of this source tree.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include <yoga/YGEnums.h>
|
||||
|
||||
namespace facebook {
|
||||
namespace yoga {
|
||||
|
||||
namespace detail {
|
||||
|
||||
constexpr size_t log2ceil(size_t n) {
|
||||
return n < 1 ? 0 : (1 + log2ceil(n / 2));
|
||||
}
|
||||
|
||||
// The number of bits necessary to represent enums defined with YG_ENUM_SEQ_DECL
|
||||
template <typename Enum>
|
||||
constexpr size_t bitWidth() {
|
||||
static_assert(
|
||||
enums::count<Enum>() > 0, "Enums must have at least one entries");
|
||||
return log2ceil(enums::count<Enum>() - 1);
|
||||
}
|
||||
|
||||
// Number of bits needed for a boolean
|
||||
template <>
|
||||
constexpr size_t bitWidth<bool>() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
template <typename U, typename... Ts>
|
||||
struct BitTraits {};
|
||||
|
||||
template <typename U>
|
||||
struct BitTraits<U> {
|
||||
// Base cases
|
||||
static constexpr size_t width(size_t) { return 0; }
|
||||
static constexpr size_t shift(size_t) { return 0; }
|
||||
};
|
||||
|
||||
template <typename U, typename T, typename... Ts>
|
||||
struct BitTraits<U, T, Ts...> {
|
||||
using Rest = BitTraits<U, Ts...>;
|
||||
|
||||
static constexpr size_t width(size_t idx) {
|
||||
return idx == 0 ? bitWidth<T>() : Rest::width(idx - 1);
|
||||
}
|
||||
|
||||
static constexpr size_t shift(size_t idx) {
|
||||
return idx == 0 ? Rest::width(0) + Rest::shift(0) : Rest::shift(idx - 1);
|
||||
}
|
||||
|
||||
static constexpr U mask(size_t idx) {
|
||||
return ((U{1} << width(idx)) - 1) << shift(idx);
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t Idx, typename T, typename... Ts>
|
||||
struct IndexedType {
|
||||
using Type = typename IndexedType<Idx - 1, Ts...>::Type;
|
||||
};
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
struct IndexedType<0, T, Ts...> {
|
||||
using Type = T;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename Storage, typename... Fields>
|
||||
class Bitfield {
|
||||
static_assert(
|
||||
std::is_integral<Storage>::value,
|
||||
"Bitfield needs an integral storage type");
|
||||
static_assert(
|
||||
std::is_unsigned<Storage>::value,
|
||||
"Bitfield needs an unsigned storage type");
|
||||
static_assert(sizeof...(Fields) > 0, "Bitfield needs at least one member");
|
||||
|
||||
using BitTraits = detail::BitTraits<Storage, Fields...>;
|
||||
|
||||
#if !defined(_MSC_VER) || _MSC_VER > 1914
|
||||
static_assert(
|
||||
BitTraits::shift(0) + BitTraits::width(0) <=
|
||||
std::numeric_limits<Storage>::digits,
|
||||
"Specified storage type is too narrow to hold all types");
|
||||
#endif
|
||||
|
||||
template <size_t Idx>
|
||||
using TypeAt = typename detail::IndexedType<Idx, Fields...>::Type;
|
||||
|
||||
template <size_t Idx>
|
||||
class Ref {
|
||||
Bitfield& bitfield_;
|
||||
|
||||
public:
|
||||
Ref(Bitfield& bitfield) : bitfield_(bitfield) {}
|
||||
Ref& operator=(TypeAt<Idx> value) {
|
||||
bitfield_.storage_ = (bitfield_.storage_ & ~BitTraits::mask(Idx)) |
|
||||
((value << BitTraits::shift(Idx)) & BitTraits::mask(Idx));
|
||||
return *this;
|
||||
}
|
||||
operator TypeAt<Idx>() const {
|
||||
return const_cast<const Bitfield&>(bitfield_).at<Idx>();
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t Idx, typename Value, typename... Values>
|
||||
static constexpr Storage initStorage(Value value, Values... values) {
|
||||
return ((value << BitTraits::shift(Idx)) & BitTraits::mask(Idx)) |
|
||||
initStorage<Idx + 1, Values...>(values...);
|
||||
}
|
||||
|
||||
template <size_t Idx>
|
||||
static constexpr Storage initStorage() {
|
||||
return Storage{0};
|
||||
}
|
||||
|
||||
Storage storage_ = 0;
|
||||
|
||||
public:
|
||||
constexpr Bitfield() = default;
|
||||
constexpr Bitfield(Fields... values) : storage_{initStorage<0>(values...)} {}
|
||||
|
||||
template <size_t Idx>
|
||||
constexpr TypeAt<Idx> at() const {
|
||||
return static_cast<TypeAt<Idx>>(
|
||||
(storage_ & BitTraits::mask(Idx)) >> BitTraits::shift(Idx));
|
||||
}
|
||||
|
||||
template <size_t Idx>
|
||||
Ref<Idx> at() {
|
||||
return {*this};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace yoga
|
||||
} // namespace facebook
|
Reference in New Issue
Block a user