diff --git a/tests/BitfieldTest.cpp b/tests/BitfieldTest.cpp new file mode 100644 index 00000000..6430f159 --- /dev/null +++ b/tests/BitfieldTest.cpp @@ -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 + +#include +#include + +namespace facebook { +namespace yoga { + +TEST(Bitfield, one_boolean_defaults_to_false) { + constexpr auto bf = Bitfield{}; + + 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{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{}; + + bf.at<0>() = true; + ASSERT_EQ(bf.at<0>(), true); +} + +TEST(Bitfield, second_boolean_defaults_to_false) { + constexpr auto bf = Bitfield{}; + + 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{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{}; + + 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{}; + + 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{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{}; + + 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{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{}; + + 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{}; + + 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{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{}; + + bf.at<0>() = YGAlignSpaceBetween; + + ASSERT_EQ(bf.at<0>(), YGAlignSpaceBetween); +} + +TEST(Bitfield, second_enum_defaults_to_0) { + constexpr auto bf = Bitfield{}; + + 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{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{}; + + 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{}; + + 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{ + 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{}; + + 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{ + (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{}; + + 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 diff --git a/yoga/Bitfield.h b/yoga/Bitfield.h new file mode 100644 index 00000000..e5b40267 --- /dev/null +++ b/yoga/Bitfield.h @@ -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 +#include +#include +#include + +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 +constexpr size_t bitWidth() { + static_assert( + enums::count() > 0, "Enums must have at least one entries"); + return log2ceil(enums::count() - 1); +} + +// Number of bits needed for a boolean +template <> +constexpr size_t bitWidth() { + return 1; +} + +template +struct BitTraits {}; + +template +struct BitTraits { + // Base cases + static constexpr size_t width(size_t) { return 0; } + static constexpr size_t shift(size_t) { return 0; } +}; + +template +struct BitTraits { + using Rest = BitTraits; + + static constexpr size_t width(size_t idx) { + return idx == 0 ? bitWidth() : 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 +struct IndexedType { + using Type = typename IndexedType::Type; +}; + +template +struct IndexedType<0, T, Ts...> { + using Type = T; +}; + +} // namespace detail + +template +class Bitfield { + static_assert( + std::is_integral::value, + "Bitfield needs an integral storage type"); + static_assert( + std::is_unsigned::value, + "Bitfield needs an unsigned storage type"); + static_assert(sizeof...(Fields) > 0, "Bitfield needs at least one member"); + + using BitTraits = detail::BitTraits; + +#if !defined(_MSC_VER) || _MSC_VER > 1914 + static_assert( + BitTraits::shift(0) + BitTraits::width(0) <= + std::numeric_limits::digits, + "Specified storage type is too narrow to hold all types"); +#endif + + template + using TypeAt = typename detail::IndexedType::Type; + + template + class Ref { + Bitfield& bitfield_; + + public: + Ref(Bitfield& bitfield) : bitfield_(bitfield) {} + Ref& operator=(TypeAt value) { + bitfield_.storage_ = (bitfield_.storage_ & ~BitTraits::mask(Idx)) | + ((value << BitTraits::shift(Idx)) & BitTraits::mask(Idx)); + return *this; + } + operator TypeAt() const { + return const_cast(bitfield_).at(); + } + }; + + template + static constexpr Storage initStorage(Value value, Values... values) { + return ((value << BitTraits::shift(Idx)) & BitTraits::mask(Idx)) | + initStorage(values...); + } + + template + static constexpr Storage initStorage() { + return Storage{0}; + } + + Storage storage_ = 0; + +public: + constexpr Bitfield() = default; + constexpr Bitfield(Fields... values) : storage_{initStorage<0>(values...)} {} + + template + constexpr TypeAt at() const { + return static_cast>( + (storage_ & BitTraits::mask(Idx)) >> BitTraits::shift(Idx)); + } + + template + Ref at() { + return {*this}; + } +}; + +} // namespace yoga +} // namespace facebook