From 4f63e56303e4d001e6b5731110b7ebddf57627fc Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Sat, 23 Dec 2023 12:21:59 -0800 Subject: [PATCH] ValuePool (#1534) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: | | Before | After | Δ | | `sizeof(yoga::Style)` | 208B | 144B | -64B/-31% | | `sizeof(yoga::Node)` | 640B | 576B | -64B/-10% | | `sizeof(YogaLayoutableShadowNode) ` | 920B | 856B | -64B/-7% | | `sizeof(YogaLayoutableShadowNode) + sizeof(YogaStylableProps)` | 1296B | 1168B | -128B/-10% | | `sizeof(ViewShadowNode)` | 920B | 856B | -64B/-7% | | `sizeof(ViewShadowNode) + sizeof(ViewShadowNodeProps)` | 2000B | 1872B | -128B/-6% | Differential Revision: D52223122 --- tests/CompactValueTest.cpp | 291 --------------------------------- tests/SmallValueBufferTest.cpp | 96 +++++++++++ tests/StyleValuePoolTest.cpp | 22 +++ yoga/YGNodeStyle.cpp | 16 +- yoga/node/Node.h | 8 +- yoga/style/CompactValue.h | 177 -------------------- yoga/style/SmallValueBuffer.h | 128 +++++++++++++++ yoga/style/Style.h | 146 +++++++++++------ yoga/style/StyleValueHandle.h | 98 +++++++++++ yoga/style/StyleValuePool.h | 123 ++++++++++++++ 10 files changed, 571 insertions(+), 534 deletions(-) delete mode 100644 tests/CompactValueTest.cpp create mode 100644 tests/SmallValueBufferTest.cpp create mode 100644 tests/StyleValuePoolTest.cpp delete mode 100644 yoga/style/CompactValue.h create mode 100644 yoga/style/SmallValueBuffer.h create mode 100644 yoga/style/StyleValueHandle.h create mode 100644 yoga/style/StyleValuePool.h diff --git a/tests/CompactValueTest.cpp b/tests/CompactValueTest.cpp deleted file mode 100644 index 32e31cd08f..0000000000 --- a/tests/CompactValueTest.cpp +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -#define YOGA_COMPACT_VALUE_TEST - -#include -#include -#include - -using namespace facebook::yoga; - -const auto tooSmall = nextafterf(CompactValue::LOWER_BOUND, -INFINITY); -const auto tooLargePoints = - nextafterf(CompactValue::UPPER_BOUND_POINT, INFINITY); -const auto tooLargePercent = - nextafterf(CompactValue::UPPER_BOUND_PERCENT, INFINITY); - -TEST(YogaTest, compact_value_can_represent_undefined) { - auto c = CompactValue{value::undefined()}; - auto v = (StyleLength)c; - ASSERT_EQ(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_NE(v, value::points(-1.25)); - ASSERT_NE(v, value::percent(25)); - ASSERT_TRUE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST(YogaTest, compact_value_manages_infinity_as_undefined) { - auto c = CompactValue{value::points(std::numeric_limits::infinity())}; - auto v = (StyleLength)c; - ASSERT_EQ(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_NE(v, value::points(-1.25)); - ASSERT_NE(v, value::percent(25)); - ASSERT_TRUE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST(YogaTest, compact_value_can_represent_auto) { - auto c = CompactValue{value::ofAuto()}; - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_EQ(v, value::ofAuto()); - ASSERT_NE(v, value::points(-1.25)); - ASSERT_NE(v, value::percent(25)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_TRUE(c.isAuto()); -} - -TEST(YogaTest, compact_value_can_represent_zero_points) { - auto c = CompactValue{value::points(0)}; - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_EQ(v, value::points(0)); - ASSERT_NE(v, value::percent(0)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST(YogaTest, compact_value_can_represent_lower_bound_points) { - auto c = CompactValue({value::points(CompactValue::LOWER_BOUND)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_EQ(v, value::points(CompactValue::LOWER_BOUND)); - ASSERT_NE(v, value::percent(CompactValue::LOWER_BOUND)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST(YogaTest, compact_value_can_represent_negative_lower_bound_points) { - auto c = CompactValue({value::points(-CompactValue::LOWER_BOUND)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_EQ(v, value::points(-CompactValue::LOWER_BOUND)); - ASSERT_NE(v, value::percent(-CompactValue::LOWER_BOUND)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST(YogaTest, compact_value_clamps_smaller_than_lower_bound_points_to_zero) { - auto c = CompactValue({value::points(tooSmall)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_EQ(v, value::points(0)); - ASSERT_NE(v, value::percent(0)); -} - -TEST( - YogaTest, - compact_value_clamps_greater_than_negative_lower_bound_points_to_zero) { - auto c = CompactValue({value::points(-tooSmall)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_EQ(v, value::points(0)); - ASSERT_NE(v, value::percent(0)); -} - -TEST(YogaTest, compact_value_can_represent_upper_bound_points) { - auto c = CompactValue({value::points(CompactValue::UPPER_BOUND_POINT)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_EQ(v, value::points(CompactValue::UPPER_BOUND_POINT)); - ASSERT_NE(v, value::percent(CompactValue::UPPER_BOUND_POINT)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST(YogaTest, compact_value_can_represent_negative_upper_bound_points) { - auto c = CompactValue({value::points(-CompactValue::UPPER_BOUND_POINT)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_EQ(v, value::points(-CompactValue::UPPER_BOUND_POINT)); - ASSERT_NE(v, value::percent(-CompactValue::UPPER_BOUND_POINT)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST( - YogaTest, - compact_value_clamps_greater_than__upper_bound_points_to_upper_bound) { - auto c = CompactValue({value::points(tooLargePoints)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_EQ(v, value::points(CompactValue::UPPER_BOUND_POINT)); - ASSERT_NE(v, value::percent(CompactValue::UPPER_BOUND_POINT)); -} - -TEST( - YogaTest, - compact_value_clamps_smaller_than_negative_upper_bound_points_to_upper_bound) { - auto c = CompactValue({value::points(-tooLargePoints)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_EQ(v, value::points(-CompactValue::UPPER_BOUND_POINT)); - ASSERT_NE(v, value::percent(-CompactValue::UPPER_BOUND_POINT)); -} - -TEST(YogaTest, compact_value_can_represent_one_point) { - auto c = CompactValue({value::points(1)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_EQ(v, value::points(1)); - ASSERT_NE(v, value::percent(1)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST(YogaTest, compact_value_can_represent_negative_one_point) { - auto c = CompactValue({value::points(-1)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_EQ(v, value::points(-1)); - ASSERT_NE(v, value::percent(-1)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST(YogaTest, compact_value_can_represent_zero_percent) { - auto c = CompactValue{value::percent(0)}; - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_NE(v, value::points(0)); - ASSERT_EQ(v, value::percent(0)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST(YogaTest, compact_value_can_represent_lower_bound_percent) { - auto c = CompactValue({value::percent(CompactValue::LOWER_BOUND)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_NE(v, value::points(CompactValue::LOWER_BOUND)); - ASSERT_EQ(v, value::percent(CompactValue::LOWER_BOUND)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST(YogaTest, compact_value_can_represent_negative_lower_bound_percent) { - auto c = CompactValue({value::percent(-CompactValue::LOWER_BOUND)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_NE(v, value::points(-CompactValue::LOWER_BOUND)); - ASSERT_EQ(v, value::percent(-CompactValue::LOWER_BOUND)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST(YogaTest, compact_value_clamps_smaller_than_lower_bound_percent_to_zero) { - auto c = CompactValue({value::percent(tooSmall)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_NE(v, value::points(0)); - ASSERT_EQ(v, value::percent(0)); -} - -TEST( - YogaTest, - compact_value_clamps_greater_than_negative_lower_bound_percent_to_zero) { - auto c = CompactValue({value::percent(-tooSmall)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_NE(v, value::points(0)); - ASSERT_EQ(v, value::percent(0)); -} - -TEST(YogaTest, compact_value_can_represent_upper_bound_percent) { - auto c = CompactValue({value::percent(CompactValue::UPPER_BOUND_PERCENT)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_NE(v, value::points(CompactValue::UPPER_BOUND_PERCENT)); - ASSERT_EQ(v, value::percent(CompactValue::UPPER_BOUND_PERCENT)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST(YogaTest, compact_value_can_represent_negative_upper_bound_percent) { - auto c = CompactValue({value::percent(-CompactValue::UPPER_BOUND_PERCENT)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_NE(v, value::points(-CompactValue::UPPER_BOUND_PERCENT)); - ASSERT_EQ(v, value::percent(-CompactValue::UPPER_BOUND_PERCENT)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST( - YogaTest, - compact_value_clamps_greater_than_upper_bound_percent_to_upper_bound) { - auto c = CompactValue({value::percent(tooLargePercent)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_NE(v, value::points(CompactValue::UPPER_BOUND_PERCENT)); - ASSERT_EQ(v, value::percent(CompactValue::UPPER_BOUND_PERCENT)); -} - -TEST( - YogaTest, - compact_value_clamps_smaller_than_negative_upper_bound_percent_to_upper_bound) { - auto c = CompactValue({value::percent(-tooLargePercent)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_NE(v, value::points(-CompactValue::UPPER_BOUND_PERCENT)); - ASSERT_EQ(v, value::percent(-CompactValue::UPPER_BOUND_PERCENT)); -} - -TEST(YogaTest, compact_value_can_represent_one_percent) { - auto c = CompactValue({value::percent(1)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_NE(v, value::points(1)); - ASSERT_EQ(v, value::percent(1)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} - -TEST(YogaTest, compact_value_can_represent_negative_one_percent) { - auto c = CompactValue({value::percent(-1)}); - auto v = (StyleLength)c; - ASSERT_NE(v, value::undefined()); - ASSERT_NE(v, value::ofAuto()); - ASSERT_NE(v, value::points(-1)); - ASSERT_EQ(v, value::percent(-1)); - ASSERT_FALSE(c.isUndefined()); - ASSERT_FALSE(c.isAuto()); -} diff --git a/tests/SmallValueBufferTest.cpp b/tests/SmallValueBufferTest.cpp new file mode 100644 index 0000000000..e12bb86fc4 --- /dev/null +++ b/tests/SmallValueBufferTest.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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 + +namespace facebook::yoga { + +constexpr size_t kBufferSize = 4; + +TEST(SmallValueBuffer, copy_assignment_with_overflow) { + std::array handles; + + SmallValueBuffer buffer1; + for (size_t i = 0; i < kBufferSize + 1; ++i) { + handles[i] = buffer1.push(static_cast(i)); + } + + SmallValueBuffer buffer2 = buffer1; + for (size_t i = 0; i < kBufferSize + 1; ++i) { + EXPECT_EQ(buffer2.get32(handles[i]), i); + } + + auto handle = buffer1.push(42u); + EXPECT_EQ(buffer1.get32(handle), 42); + + EXPECT_THROW({ buffer2.get32(handle); }, std::logic_error); +} + +TEST(SmallValueBuffer, push_32) { + auto magic = 88567114u; + + SmallValueBuffer buffer; + auto handle = buffer.push(magic); + EXPECT_EQ(buffer.get32(handle), magic); +} + +TEST(SmallValueBuffer, push_overflow) { + uint32_t magic1 = 88567114u; + uint32_t magic2 = 351012214u; + uint32_t magic3 = 146122128u; + uint32_t magic4 = 2171092154u; + uint32_t magic5 = 2269016953u; + + SmallValueBuffer buffer; + EXPECT_EQ(buffer.get32(buffer.push(magic1)), magic1); + EXPECT_EQ(buffer.get32(buffer.push(magic2)), magic2); + EXPECT_EQ(buffer.get32(buffer.push(magic3)), magic3); + EXPECT_EQ(buffer.get32(buffer.push(magic4)), magic4); + EXPECT_EQ(buffer.get32(buffer.push(magic5)), magic5); +} + +TEST(SmallValueBuffer, push_64) { + uint64_t magic = 88567114ull; + + SmallValueBuffer buffer; + auto handle = buffer.push(magic); + EXPECT_EQ(buffer.get64(handle), magic); +} + +TEST(SmallValueBuffer, push_64_overflow) { + uint64_t magic1 = 1401612388342512ull; + uint64_t magic2 = 118712305386210ull; + uint64_t magic3 = 752431801563359011ull; + uint64_t magic4 = 118138934255546108ull; + uint64_t magic5 = 237115443124116111ull; + + SmallValueBuffer buffer; + EXPECT_EQ(buffer.get64(buffer.push(magic1)), magic1); + EXPECT_EQ(buffer.get64(buffer.push(magic2)), magic2); + EXPECT_EQ(buffer.get64(buffer.push(magic3)), magic3); + EXPECT_EQ(buffer.get64(buffer.push(magic4)), magic4); + EXPECT_EQ(buffer.get64(buffer.push(magic5)), magic5); +} + +TEST(SmallValueBuffer, push_64_after_32) {} + +TEST(SmallValueBuffer, push_32_after_64) {} + +TEST(SmallValueBuffer, get32_oob) {} + +TEST(SmallValueBuffer, get64_oob) {} + +TEST(SmallValueBuffer, replace_32_with_32) {} + +TEST(SmallValueBuffer, replace_32_with_64) {} + +TEST(SmallValueBuffer, replace_64_with_32) {} + +TEST(SmallValueBuffer, replace_64_with_64) {} + +} // namespace facebook::yoga diff --git a/tests/StyleValuePoolTest.cpp b/tests/StyleValuePoolTest.cpp new file mode 100644 index 0000000000..4ab498948b --- /dev/null +++ b/tests/StyleValuePoolTest.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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 + +namespace facebook::yoga { + +TEST(StyleValuePool, store_small_int_points) { + StyleValuePool pool; + + StyleValueHandle handle; + pool.store(handle, value::points(10)); + + EXPECT_EQ(pool.getLength(handle), value::points(10)); +} + +} // namespace facebook::yoga diff --git a/yoga/YGNodeStyle.cpp b/yoga/YGNodeStyle.cpp index 8ccb94e302..7c984ced51 100644 --- a/yoga/YGNodeStyle.cpp +++ b/yoga/YGNodeStyle.cpp @@ -34,15 +34,13 @@ void updateStyle(YGNodeRef node, IdxT idx, ValueT value) { } // namespace -void YGNodeCopyStyle( - const YGNodeRef dstNodeRef, - const YGNodeConstRef srcNodeRef) { - auto dstNode = resolveRef(dstNodeRef); - auto srcNode = resolveRef(srcNodeRef); - - if (!(dstNode->getStyle() == srcNode->getStyle())) { - dstNode->setStyle(srcNode->getStyle()); - dstNode->markDirtyAndPropagate(); +void YGNodeCopyStyle(YGNodeRef dstNode, YGNodeConstRef srcNode) { + auto dst = resolveRef(dstNode); + auto src = resolveRef(srcNode); + + if (dst->getStyle() != src->getStyle()) { + dst->setStyle(src->getStyle()); + dst->markDirtyAndPropagate(); } } diff --git a/yoga/node/Node.h b/yoga/node/Node.h index 712f154e68..9e19f3075d 100644 --- a/yoga/node/Node.h +++ b/yoga/node/Node.h @@ -375,13 +375,13 @@ class YG_EXPORT Node : public ::YGNode { YGBaselineFunc baselineFunc_ = {nullptr}; YGPrintFunc printFunc_ = {nullptr}; YGDirtiedFunc dirtiedFunc_ = nullptr; - Style style_ = {}; - LayoutResults layout_ = {}; + Style style_; + LayoutResults layout_; size_t lineIndex_ = 0; Node* owner_ = nullptr; - std::vector children_ = {}; + std::vector children_; const Config* config_; - std::array resolvedDimensions_ = { + std::array resolvedDimensions_{ {value::undefined(), value::undefined()}}; }; diff --git a/yoga/style/CompactValue.h b/yoga/style/CompactValue.h deleted file mode 100644 index e80f2a9f22..0000000000 --- a/yoga/style/CompactValue.h +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and 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 - -#include -#include - -#include -#include - -static_assert( - std::numeric_limits::is_iec559, - "facebook::yoga::detail::CompactValue only works with IEEE754 floats"); - -#ifdef YOGA_COMPACT_VALUE_TEST -#define VISIBLE_FOR_TESTING public: -#else -#define VISIBLE_FOR_TESTING private: -#endif - -namespace facebook::yoga { - -// This class stores YGValue in 32 bits. -// - The value does not matter for Undefined and Auto. NaNs are used for their -// representation. -// - To differentiate between Point and Percent, one exponent bit is used. -// Supported the range [0x40, 0xbf] (0xbf is inclusive for point, but -// exclusive for percent). -// - Value ranges: -// points: 1.08420217e-19f to 36893485948395847680 -// 0x00000000 0x3fffffff -// percent: 1.08420217e-19f to 18446742974197923840 -// 0x40000000 0x7f7fffff -// - Zero is supported, negative zero is not -// - values outside of the representable range are clamped -class CompactValue { - friend constexpr bool operator==(CompactValue, CompactValue) noexcept; - - public: - static constexpr auto LOWER_BOUND = 1.08420217e-19f; - static constexpr auto UPPER_BOUND_POINT = 36893485948395847680.0f; - static constexpr auto UPPER_BOUND_PERCENT = 18446742974197923840.0f; - - static constexpr CompactValue ofUndefined() noexcept { - return CompactValue{}; - } - - static constexpr CompactValue ofAuto() noexcept { - return CompactValue{AUTO_BITS}; - } - - constexpr CompactValue() noexcept = default; - - explicit constexpr CompactValue(const StyleLength& x) noexcept { - switch (x.unit()) { - case Unit::Undefined: - *this = ofUndefined(); - break; - case Unit::Auto: - *this = ofAuto(); - break; - case Unit::Point: - *this = of(x.value().unwrap()); - break; - case Unit::Percent: - *this = of(x.value().unwrap()); - break; - } - } - - explicit operator StyleLength() const noexcept { - if (repr_ == 0x7FC00000) { - return value::undefined(); - } - - switch (repr_) { - case AUTO_BITS: - return value::ofAuto(); - case ZERO_BITS_POINT: - return value::points(0); - case ZERO_BITS_PERCENT: - return value::percent(0); - } - - auto data = repr_; - data &= ~PERCENT_BIT; - data += BIAS; - - if (repr_ & 0x40000000) { - return value::percent(std::bit_cast(data)); - } else { - return value::points(std::bit_cast(data)); - } - } - - bool isUndefined() const noexcept { - return ( - repr_ != AUTO_BITS && repr_ != ZERO_BITS_POINT && - repr_ != ZERO_BITS_PERCENT && std::isnan(std::bit_cast(repr_))); - } - - bool isDefined() const noexcept { - return !isUndefined(); - } - - bool isAuto() const noexcept { - return repr_ == AUTO_BITS; - } - - private: - template - static CompactValue of(float value) noexcept { - if (value == 0.0f || (value < LOWER_BOUND && value > -LOWER_BOUND)) { - constexpr auto zero = - UnitT == Unit::Percent ? ZERO_BITS_PERCENT : ZERO_BITS_POINT; - return {zero}; - } - - constexpr auto upperBound = - UnitT == Unit::Percent ? UPPER_BOUND_PERCENT : UPPER_BOUND_POINT; - if (value > upperBound || value < -upperBound) { - value = copysignf(upperBound, value); - } - - uint32_t unitBit = UnitT == Unit::Percent ? PERCENT_BIT : 0; - auto data = std::bit_cast(value); - data -= BIAS; - data |= unitBit; - return {data}; - } - - uint32_t repr_{0x7FC00000}; - - static constexpr uint32_t BIAS = 0x20000000; - static constexpr uint32_t PERCENT_BIT = 0x40000000; - - // these are signaling NaNs with specific bit pattern as payload they will be - // silenced whenever going through an FPU operation on ARM + x86 - static constexpr uint32_t AUTO_BITS = 0x7faaaaaa; - static constexpr uint32_t ZERO_BITS_POINT = 0x7f8f0f0f; - static constexpr uint32_t ZERO_BITS_PERCENT = 0x7f80f0f0; - - constexpr CompactValue(uint32_t data) noexcept : repr_(data) {} - - VISIBLE_FOR_TESTING uint32_t repr() { - return repr_; - } -}; - -template <> -CompactValue CompactValue::of(float) noexcept = delete; -template <> -CompactValue CompactValue::of(float) noexcept = delete; - -constexpr bool operator==(CompactValue a, CompactValue b) noexcept { - return a.repr_ == b.repr_; -} - -constexpr bool operator!=(CompactValue a, CompactValue b) noexcept { - return !(a == b); -} - -inline bool inexactEquals(CompactValue a, CompactValue b) { - return inexactEquals((StyleLength)a, (StyleLength)b); -} - -} // namespace facebook::yoga diff --git a/yoga/style/SmallValueBuffer.h b/yoga/style/SmallValueBuffer.h new file mode 100644 index 0000000000..098e912f27 --- /dev/null +++ b/yoga/style/SmallValueBuffer.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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::yoga { + +// Container which allows storing 32 or 64 bit integer values, whose index may +// never change. Values are first stored in a fixed buffer of `BufferSize` +// 32-bit chunks, before falling back to heap allocation. +template +class SmallValueBuffer { + public: + SmallValueBuffer() = default; + SmallValueBuffer(const SmallValueBuffer& other) { + *this = other; + } + SmallValueBuffer(SmallValueBuffer&& other) = default; + + // Add a new element to the buffer, returning the index of the element + uint16_t push(uint32_t value) { + const auto index = count_++; + if (index < buffer_.size()) { + buffer_[index] = value; + return index; + } + + if (overflow_ == nullptr) { + overflow_ = std::make_unique(); + } + + overflow_->buffer_.push_back(value); + overflow_->wideElements_.push_back(false); + return index; + } + + uint16_t push(uint64_t value) { + const auto lsb = static_cast(value & 0xFFFFFFFF); + const auto msb = static_cast(value >> 32); + + const auto lsbIndex = push(lsb); + push(msb); + + if (lsbIndex < buffer_.size()) { + wideElements_[lsbIndex] = true; + } else { + overflow_->wideElements_[lsbIndex - buffer_.size()] = true; + } + return lsbIndex; + } + + // Replace an existing element in the buffer with a new value. A new index + // may be returned, e.g. if a new value is wider than the previous. + [[nodiscard]] uint16_t replace(uint16_t index, uint32_t value) { + if (index < buffer_.size()) { + buffer_[index] = value; + } else { + overflow_->buffer_.at(index - buffer_.size()) = value; + } + + return index; + } + + [[nodiscard]] uint16_t replace(uint16_t index, uint64_t value) { + const bool isWide = index < wideElements_.size() + ? wideElements_[index] + : overflow_->wideElements_.test(index - buffer_.size()); + + if (isWide) { + const auto lsb = static_cast(value & 0xFFFFFFFF); + const auto msb = static_cast(value >> 32); + + replace(index, lsb); + replace(index + 1, msb); + return index; + } else { + return push(value); + } + } + + // Get a value of a given width + uint32_t get32(uint16_t index) const { + if (index < buffer_.size()) { + return buffer_[index]; + } else { + return overflow_->buffer_.at(index - buffer_.size()); + } + } + + uint64_t get64(uint16_t index) const { + const auto lsb = get32(index); + const auto msb = get32(index + 1); + return (static_cast(msb) << 32) | lsb; + } + + SmallValueBuffer& operator=(const SmallValueBuffer& other) { + count_ = other.count_; + buffer_ = other.buffer_; + wideElements_ = other.wideElements_; + overflow_ = other.overflow_ ? std::make_unique(*other.overflow_) + : nullptr; + return *this; + } + + SmallValueBuffer& operator=(SmallValueBuffer&& other) = default; + + private: + struct Overflow { + std::vector buffer_; + std::vector wideElements_; + }; + + uint16_t count_{0}; + std::array buffer_; + std::bitset wideElements_; + std::unique_ptr overflow_; +}; + +} // namespace facebook::yoga diff --git a/yoga/style/Style.h b/yoga/style/Style.h index 2993337539..978cca5a54 100644 --- a/yoga/style/Style.h +++ b/yoga/style/Style.h @@ -27,8 +27,8 @@ #include #include #include -#include #include +#include namespace facebook::yoga { @@ -111,109 +111,109 @@ class YG_EXPORT Style { } FloatOptional flex() const { - return flex_; + return pool_.getNumber(flex_); } void setFlex(FloatOptional value) { - flex_ = value; + pool_.store(flex_, value); } FloatOptional flexGrow() const { - return flexGrow_; + return pool_.getNumber(flexGrow_); } void setFlexGrow(FloatOptional value) { - flexGrow_ = value; + pool_.store(flexGrow_, value); } FloatOptional flexShrink() const { - return flexShrink_; + return pool_.getNumber(flexShrink_); } void setFlexShrink(FloatOptional value) { - flexShrink_ = value; + pool_.store(flexShrink_, value); } Style::Length flexBasis() const { - return (Style::Length)flexBasis_; + return pool_.getLength(flexBasis_); } void setFlexBasis(Style::Length value) { - flexBasis_ = CompactValue(value); + pool_.store(flexBasis_, value); } Style::Length margin(Edge edge) const { - return (Style::Length)margin_[yoga::to_underlying(edge)]; + return pool_.getLength(margin_[yoga::to_underlying(edge)]); } void setMargin(Edge edge, Style::Length value) { - margin_[yoga::to_underlying(edge)] = CompactValue(value); + pool_.store(margin_[yoga::to_underlying(edge)], value); } Style::Length position(Edge edge) const { - return (Style::Length)position_[yoga::to_underlying(edge)]; + return pool_.getLength(position_[yoga::to_underlying(edge)]); } void setPosition(Edge edge, Style::Length value) { - position_[yoga::to_underlying(edge)] = CompactValue(value); + pool_.store(position_[yoga::to_underlying(edge)], value); } Style::Length padding(Edge edge) const { - return (Style::Length)padding_[yoga::to_underlying(edge)]; + return pool_.getLength(padding_[yoga::to_underlying(edge)]); } void setPadding(Edge edge, Style::Length value) { - padding_[yoga::to_underlying(edge)] = CompactValue(value); + pool_.store(padding_[yoga::to_underlying(edge)], value); } Style::Length border(Edge edge) const { - return (Style::Length)border_[yoga::to_underlying(edge)]; + return pool_.getLength(border_[yoga::to_underlying(edge)]); } void setBorder(Edge edge, Style::Length value) { - border_[yoga::to_underlying(edge)] = CompactValue(value); + pool_.store(border_[yoga::to_underlying(edge)], value); } Style::Length gap(Gutter gutter) const { - return (Style::Length)gap_[yoga::to_underlying(gutter)]; + return pool_.getLength(gap_[yoga::to_underlying(gutter)]); } void setGap(Gutter gutter, Style::Length value) { - gap_[yoga::to_underlying(gutter)] = CompactValue(value); + pool_.store(gap_[yoga::to_underlying(gutter)], value); } Style::Length dimension(Dimension axis) const { - return (Style::Length)dimensions_[yoga::to_underlying(axis)]; + return pool_.getLength(dimensions_[yoga::to_underlying(axis)]); } void setDimension(Dimension axis, Style::Length value) { - dimensions_[yoga::to_underlying(axis)] = CompactValue(value); + pool_.store(dimensions_[yoga::to_underlying(axis)], value); } Style::Length minDimension(Dimension axis) const { - return (Style::Length)minDimensions_[yoga::to_underlying(axis)]; + return pool_.getLength(minDimensions_[yoga::to_underlying(axis)]); } void setMinDimension(Dimension axis, Style::Length value) { - minDimensions_[yoga::to_underlying(axis)] = CompactValue(value); + pool_.store(minDimensions_[yoga::to_underlying(axis)], value); } Style::Length maxDimension(Dimension axis) const { - return (Style::Length)maxDimensions_[yoga::to_underlying(axis)]; + return pool_.getLength(maxDimensions_[yoga::to_underlying(axis)]); } void setMaxDimension(Dimension axis, Style::Length value) { - maxDimensions_[yoga::to_underlying(axis)] = CompactValue(value); + pool_.store(maxDimensions_[yoga::to_underlying(axis)], value); } FloatOptional aspectRatio() const { - return aspectRatio_; + return pool_.getNumber(aspectRatio_); } void setAspectRatio(FloatOptional value) { - aspectRatio_ = value; + pool_.store(aspectRatio_, value); } Style::Length resolveColumnGap() const { if (gap_[yoga::to_underlying(Gutter::Column)].isDefined()) { - return (Style::Length)gap_[yoga::to_underlying(Gutter::Column)]; + return pool_.getLength(gap_[yoga::to_underlying(Gutter::Column)]); } else { - return (Style::Length)gap_[yoga::to_underlying(Gutter::All)]; + return pool_.getLength(gap_[yoga::to_underlying(Gutter::All)]); } } Style::Length resolveRowGap() const { if (gap_[yoga::to_underlying(Gutter::Row)].isDefined()) { - return (Style::Length)gap_[yoga::to_underlying(Gutter::Row)]; + return pool_.getLength(gap_[yoga::to_underlying(Gutter::Row)]); } else { - return (Style::Length)gap_[yoga::to_underlying(Gutter::All)]; + return pool_.getLength(gap_[yoga::to_underlying(Gutter::All)]); } } @@ -241,19 +241,21 @@ class YG_EXPORT Style { alignItems_ == other.alignItems_ && alignSelf_ == other.alignSelf_ && positionType_ == other.positionType_ && flexWrap_ == other.flexWrap_ && overflow_ == other.overflow_ && display_ == other.display_ && - inexactEquals(flex_, other.flex_) && - inexactEquals(flexGrow_, other.flexGrow_) && - inexactEquals(flexShrink_, other.flexShrink_) && - inexactEquals(flexBasis_, other.flexBasis_) && - inexactEquals(margin_, other.margin_) && - inexactEquals(position_, other.position_) && - inexactEquals(padding_, other.padding_) && - inexactEquals(border_, other.border_) && - inexactEquals(gap_, other.gap_) && - inexactEquals(dimensions_, other.dimensions_) && - inexactEquals(minDimensions_, other.minDimensions_) && - inexactEquals(maxDimensions_, other.maxDimensions_) && - inexactEquals(aspectRatio_, other.aspectRatio_); + numbersEqual(flex_, pool_, other.flex_, other.pool_) && + numbersEqual(flexGrow_, pool_, other.flexGrow_, other.pool_) && + numbersEqual(flexShrink_, pool_, other.flexShrink_, other.pool_) && + lengthsEqual(flexBasis_, pool_, other.flexBasis_, other.pool_) && + lengthsEqual(margin_, pool_, other.margin_, other.pool_) && + lengthsEqual(position_, pool_, other.position_, other.pool_) && + lengthsEqual(padding_, pool_, other.padding_, other.pool_) && + lengthsEqual(border_, pool_, other.border_, other.pool_) && + lengthsEqual(gap_, pool_, other.gap_, other.pool_) && + lengthsEqual(dimensions_, pool_, other.dimensions_, other.pool_) && + lengthsEqual( + minDimensions_, pool_, other.minDimensions_, other.pool_) && + lengthsEqual( + maxDimensions_, pool_, other.maxDimensions_, other.pool_) && + numbersEqual(aspectRatio_, pool_, other.aspectRatio_, other.pool_); } bool operator!=(const Style& other) const { @@ -261,9 +263,43 @@ class YG_EXPORT Style { } private: - using Dimensions = std::array()>; - using Edges = std::array()>; - using Gutters = std::array()>; + using Dimensions = std::array()>; + using Edges = std::array()>; + using Gutters = std::array()>; + + static inline bool numbersEqual( + const StyleValueHandle& lhsHandle, + const StyleValuePool& lhsPool, + const StyleValueHandle& rhsHandle, + const StyleValuePool& rhsPool) { + return (lhsHandle.isUndefined() && rhsHandle.isUndefined()) || + (lhsPool.getNumber(lhsHandle) == rhsPool.getNumber(rhsHandle)); + } + + static inline bool lengthsEqual( + const StyleValueHandle& lhsHandle, + const StyleValuePool& lhsPool, + const StyleValueHandle& rhsHandle, + const StyleValuePool& rhsPool) { + return (lhsHandle.isUndefined() && rhsHandle.isUndefined()) || + (lhsPool.getLength(lhsHandle) == rhsPool.getLength(rhsHandle)); + } + + static inline bool lengthsEqual( + const std::ranges::range auto& lhs, + const StyleValuePool& lhsPool, + const std::ranges::range auto& rhs, + const StyleValuePool& rhsPool) { + // TODO: libc++ 14 doesn't support `std::ranges::equal` + return std::equal( + lhs.begin(), + lhs.end(), + rhs.begin(), + rhs.end(), + [&](const auto& lhs, const auto& rhs) { + return lengthsEqual(lhs, lhsPool, rhs, rhsPool); + }); + } Direction direction_ : bitCount() = Direction::Inherit; FlexDirection flexDirection_ @@ -278,19 +314,23 @@ class YG_EXPORT Style { Overflow overflow_ : bitCount() = Overflow::Visible; Display display_ : bitCount() = Display::Flex; - FloatOptional flex_{}; - FloatOptional flexGrow_{}; - FloatOptional flexShrink_{}; - CompactValue flexBasis_{CompactValue::ofAuto()}; + StyleValueHandle flex_{}; + StyleValueHandle flexGrow_{}; + StyleValueHandle flexShrink_{}; + StyleValueHandle flexBasis_{StyleValueHandle::ofAuto()}; Edges margin_{}; Edges position_{}; Edges padding_{}; Edges border_{}; Gutters gap_{}; - Dimensions dimensions_{CompactValue::ofAuto(), CompactValue::ofAuto()}; + Dimensions dimensions_{ + StyleValueHandle::ofAuto(), + StyleValueHandle::ofAuto()}; Dimensions minDimensions_{}; Dimensions maxDimensions_{}; - FloatOptional aspectRatio_{}; + StyleValueHandle aspectRatio_{}; + + StyleValuePool pool_; }; } // namespace facebook::yoga diff --git a/yoga/style/StyleValueHandle.h b/yoga/style/StyleValueHandle.h new file mode 100644 index 0000000000..f4b97f0a9f --- /dev/null +++ b/yoga/style/StyleValueHandle.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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 +#include + +namespace facebook::yoga { + +#pragma pack(push) +#pragma pack(1) + +/** + * StyleValueHandle is a small (16-bit) handle to a length or number in a style. + * The value may be embedded directly in the handle if simple, or the handle may + * instead point to an index within a StyleValuePool. + * + * To read or write a value from a StyleValueHandle, use + * `StyleValuePool::store()`, and `StyleValuePool::getLength()`/ + * `StyleValuePool::getNumber()`. + */ +class StyleValueHandle { + public: + static constexpr StyleValueHandle ofAuto() { + StyleValueHandle handle; + handle.setType(Type::Auto); + return handle; + } + + constexpr bool isUndefined() const { + return type() == Type::Undefined; + } + + constexpr bool isDefined() const { + return !isUndefined(); + } + + constexpr bool isAuto() const { + return type() == Type::Auto; + } + + private: + friend class StyleValuePool; + + static constexpr uint16_t kHandleTypeMask = 0b0000'0000'0000'0111; + static constexpr uint16_t kHandleIndexedMask = 0b0000'0000'0000'1000; + static constexpr uint16_t kHandleValueMask = 0b1111'1111'1111'0000; + + enum class Type : uint8_t { + Undefined, + Point, + Percent, + Number, + Auto, + }; + + constexpr Type type() const { + return static_cast(repr_ & kHandleTypeMask); + } + + constexpr void setType(Type handleType) { + repr_ &= (~kHandleTypeMask); + repr_ |= static_cast(handleType); + } + + constexpr uint16_t value() const { + return repr_ >> 4; + } + + constexpr void setValue(uint16_t value) { + repr_ &= (~kHandleValueMask); + repr_ |= (value << 4); + } + + constexpr bool isValueIndexed() const { + return (repr_ & kHandleIndexedMask) != 0; + } + + constexpr void setValueIsIndexed() { + repr_ |= kHandleIndexedMask; + } + + uint16_t repr_{0}; +}; + +#pragma pack(pop) + +} // namespace facebook::yoga diff --git a/yoga/style/StyleValuePool.h b/yoga/style/StyleValuePool.h new file mode 100644 index 0000000000..a8698a6479 --- /dev/null +++ b/yoga/style/StyleValuePool.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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 +#include + +namespace facebook::yoga { + +/** + * StyleValuePool allows compact storage for a sparse collection of assigned + * lengths and numbers. Values are referred to using StyleValueHandle. In most + * cases StyleValueHandle can embed the value directly, but if not, the value is + * stored within a buffer provided by the pool. The pool contains a fixed number + * of inline slots before falling back to heap allocating additional slots. + */ +class StyleValuePool { + public: + void store(StyleValueHandle& handle, StyleLength length) { + if (length.isUndefined()) { + handle.setType(StyleValueHandle::Type::Undefined); + } else if (length.isAuto()) { + handle.setType(StyleValueHandle::Type::Auto); + } else { + auto type = length.unit() == Unit::Point + ? StyleValueHandle::Type::Point + : StyleValueHandle::Type::Percent; + storeValue(handle, length.value().unwrap(), type); + } + } + + void store(StyleValueHandle& handle, FloatOptional number) { + if (number.isUndefined()) { + handle.setType(StyleValueHandle::Type::Undefined); + } else { + storeValue(handle, number.unwrap(), StyleValueHandle::Type::Number); + } + } + + StyleLength getLength(StyleValueHandle handle) const { + if (handle.isUndefined()) { + return value::undefined(); + } else if (handle.isAuto()) { + return value::ofAuto(); + } else { + float value = (handle.isValueIndexed()) + ? std::bit_cast(buffer_.get32(handle.value())) + : unpackInlineInteger(handle.value()); + + return handle.type() == StyleValueHandle::Type::Point + ? value::points(value) + : value::percent(value); + } + } + + FloatOptional getNumber(StyleValueHandle handle) const { + if (handle.isUndefined()) { + return FloatOptional{}; + } else { + float value = (handle.isValueIndexed()) + ? std::bit_cast(buffer_.get32(handle.value())) + : unpackInlineInteger(handle.value()); + return FloatOptional{value}; + } + } + + private: + void storeValue( + StyleValueHandle& handle, + float value, + StyleValueHandle::Type type) { + handle.setType(type); + + if (handle.isValueIndexed()) { + auto newIndex = + buffer_.replace(handle.value(), std::bit_cast(value)); + handle.setValue(newIndex); + } else if (isIntegerPackable(value)) { + handle.setValue(packInlineInteger(value)); + } else { + auto newIndex = buffer_.push(std::bit_cast(value)); + handle.setValue(newIndex); + handle.setValueIsIndexed(); + } + } + + static constexpr bool isIntegerPackable(float f) { + constexpr uint16_t kMaxInlineAbsValue = (1 << 11) - 1; + + int32_t i = static_cast(f); + return static_cast(i) == f && i >= -kMaxInlineAbsValue && + i <= +kMaxInlineAbsValue; + } + + static constexpr uint16_t packInlineInteger(float value) { + uint16_t isNegative = value < 0 ? 1 : 0; + return static_cast( + (isNegative << 11) | + (static_cast(value) * (isNegative ? -1 : 1))); + } + + static constexpr float unpackInlineInteger(uint16_t value) { + const bool isNegative = (value & kHandleSignMask) != 0; + return static_cast( + (value & kHandleMagnitudeMask) * (isNegative ? -1 : 1)); + } + + static constexpr uint16_t kHandleSignMask = 0b0000'1000'0000'0000; + static constexpr uint16_t kHandleMagnitudeMask = 0b0000'0111'1111'1111; + + SmallValueBuffer<4> buffer_; +}; + +} // namespace facebook::yoga