From 92b889b89e92bb52ba2d6868bf6de4d7c3972368 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Fri, 19 Jan 2024 18:22:29 -0800 Subject: [PATCH] Replace CompactValue with StyleValueHandle and StyleValuePool (#42131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/42131 X-link: https://github.com/facebook/yoga/pull/1534 Now that the storage method is a hidden implementation detail, this changes the underlying data structure used to store styles, from `CompactValue` (a customized 32-bit float with tag bits), to `StyleValuePool`. This new structure operates on 16-bit handles, and a shared small buffer. The vast majority of real-world values can be stored directly in the handle, but we allow arbitrary 32 bit (and soon 64-bit) values to be stored, where the handle then becomes an index into the styles buffer. This results in a real-world memory usage win, while also letting us store the 64-bit values we are wanting to use for math function support (compared to doubling the storage requirements). This does seem to make style reads slower, which due to their heavy frequency, does have a performance impact observable by synthetics. In an example laying out a tree of 10,000 nodes, we originally read from `StyleValuePool` 2.4 million times. This originally resulted in a ~10% regression, but when combined with the changes in the last diff, most style reads become simple bitwise operations on the handle, and we are actually 14% faster than before. | | 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% | | "Huge nested layout" microbenchmark (M1 Ultra) | 11.5ms | 9.9ms | -1.6ms/-14% | | Quest Store C++ heap usage (avg over 10 runs) | 86.2MB | 84.9MB | -1.3MB/-1.5% | Reviewed By: joevilches Differential Revision: D52223122 fbshipit-source-id: 990f4b7e991e8e22d198ce20f7da66d9c6ba637b --- .../ReactCommon/yoga/yoga/YGNodeStyle.cpp | 16 +- .../ReactCommon/yoga/yoga/node/Node.h | 8 +- .../yoga/yoga/style/CompactValue.h | 177 ----------------- .../yoga/yoga/style/SmallValueBuffer.h | 133 +++++++++++++ .../ReactCommon/yoga/yoga/style/Style.h | 179 +++++++++++------- .../yoga/yoga/style/StyleValueHandle.h | 98 ++++++++++ .../yoga/yoga/style/StyleValuePool.h | 127 +++++++++++++ 7 files changed, 478 insertions(+), 260 deletions(-) delete mode 100644 packages/react-native/ReactCommon/yoga/yoga/style/CompactValue.h create mode 100644 packages/react-native/ReactCommon/yoga/yoga/style/SmallValueBuffer.h create mode 100644 packages/react-native/ReactCommon/yoga/yoga/style/StyleValueHandle.h create mode 100644 packages/react-native/ReactCommon/yoga/yoga/style/StyleValuePool.h diff --git a/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.cpp b/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.cpp index ac24f5f1494c4c..d8454053dee558 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/YGNodeStyle.cpp +++ b/packages/react-native/ReactCommon/yoga/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->style() == srcNode->style())) { - dstNode->setStyle(srcNode->style()); - dstNode->markDirtyAndPropagate(); +void YGNodeCopyStyle(YGNodeRef dstNode, YGNodeConstRef srcNode) { + auto dst = resolveRef(dstNode); + auto src = resolveRef(srcNode); + + if (dst->style() != src->style()) { + dst->setStyle(src->style()); + dst->markDirtyAndPropagate(); } } diff --git a/packages/react-native/ReactCommon/yoga/yoga/node/Node.h b/packages/react-native/ReactCommon/yoga/yoga/node/Node.h index d2fdb20aac4800..1eea46f8c1c51d 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/node/Node.h +++ b/packages/react-native/ReactCommon/yoga/yoga/node/Node.h @@ -274,13 +274,13 @@ class YG_EXPORT Node : public ::YGNode { YGMeasureFunc measureFunc_ = nullptr; YGBaselineFunc baselineFunc_ = 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/packages/react-native/ReactCommon/yoga/yoga/style/CompactValue.h b/packages/react-native/ReactCommon/yoga/yoga/style/CompactValue.h deleted file mode 100644 index e80f2a9f22f574..00000000000000 --- a/packages/react-native/ReactCommon/yoga/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/packages/react-native/ReactCommon/yoga/yoga/style/SmallValueBuffer.h b/packages/react-native/ReactCommon/yoga/yoga/style/SmallValueBuffer.h new file mode 100644 index 00000000000000..2860a420f9ca6c --- /dev/null +++ b/packages/react-native/ReactCommon/yoga/yoga/style/SmallValueBuffer.h @@ -0,0 +1,133 @@ +/* + * 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 + +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_++; + assert(index < 4096 && "SmallValueBuffer can only hold up to 4096 chunks"); + 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); + [[maybe_unused]] const auto msbIndex = push(msb); + assert( + msbIndex < 4096 && "SmallValueBuffer can only hold up to 4096 chunks"); + + 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_.at(index - buffer_.size()); + + if (isWide) { + const auto lsb = static_cast(value & 0xFFFFFFFF); + const auto msb = static_cast(value >> 32); + + [[maybe_unused]] auto lsbIndex = replace(index, lsb); + [[maybe_unused]] auto msbIndex = 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/packages/react-native/ReactCommon/yoga/yoga/style/Style.h b/packages/react-native/ReactCommon/yoga/yoga/style/Style.h index aae189a778db4d..028fcf17c1e500 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/style/Style.h +++ b/packages/react-native/ReactCommon/yoga/yoga/style/Style.h @@ -7,7 +7,6 @@ #pragma once -#include #include #include #include @@ -29,8 +28,8 @@ #include #include #include -#include #include +#include namespace facebook::yoga { @@ -113,94 +112,94 @@ 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); } bool horizontalInsetsDefined() const { @@ -448,19 +447,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 { @@ -468,23 +469,57 @@ 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)); + } + + template + static inline bool lengthsEqual( + const std::array& lhs, + const StyleValuePool& lhsPool, + const std::array& rhs, + const StyleValuePool& rhsPool) { + return std::equal( + lhs.begin(), + lhs.end(), + rhs.begin(), + rhs.end(), + [&](const auto& lhs, const auto& rhs) { + return lengthsEqual(lhs, lhsPool, rhs, rhsPool); + }); + } Style::Length computeColumnGap() 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 computeRowGap() 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)]); } } @@ -492,27 +527,27 @@ class YG_EXPORT Style { const { if (layoutDirection == Direction::LTR && edges[yoga::to_underlying(Edge::Start)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Start)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Start)]); } else if ( layoutDirection == Direction::RTL && edges[yoga::to_underlying(Edge::End)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::End)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::End)]); } else if (edges[yoga::to_underlying(Edge::Left)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Left)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Left)]); } else if (edges[yoga::to_underlying(Edge::Horizontal)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Horizontal)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Horizontal)]); } else { - return (Style::Length)edges[yoga::to_underlying(Edge::All)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::All)]); } } Style::Length computeTopEdge(const Edges& edges) const { if (edges[yoga::to_underlying(Edge::Top)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Top)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Top)]); } else if (edges[yoga::to_underlying(Edge::Vertical)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Vertical)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Vertical)]); } else { - return (Style::Length)edges[yoga::to_underlying(Edge::All)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::All)]); } } @@ -520,27 +555,27 @@ class YG_EXPORT Style { const { if (layoutDirection == Direction::LTR && edges[yoga::to_underlying(Edge::End)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::End)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::End)]); } else if ( layoutDirection == Direction::RTL && edges[yoga::to_underlying(Edge::Start)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Start)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Start)]); } else if (edges[yoga::to_underlying(Edge::Right)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Right)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Right)]); } else if (edges[yoga::to_underlying(Edge::Horizontal)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Horizontal)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Horizontal)]); } else { - return (Style::Length)edges[yoga::to_underlying(Edge::All)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::All)]); } } Style::Length computeBottomEdge(const Edges& edges) const { if (edges[yoga::to_underlying(Edge::Bottom)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Bottom)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Bottom)]); } else if (edges[yoga::to_underlying(Edge::Vertical)].isDefined()) { - return (Style::Length)edges[yoga::to_underlying(Edge::Vertical)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::Vertical)]); } else { - return (Style::Length)edges[yoga::to_underlying(Edge::All)]; + return pool_.getLength(edges[yoga::to_underlying(Edge::All)]); } } @@ -617,19 +652,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/packages/react-native/ReactCommon/yoga/yoga/style/StyleValueHandle.h b/packages/react-native/ReactCommon/yoga/yoga/style/StyleValueHandle.h new file mode 100644 index 00000000000000..f4b97f0a9f7d00 --- /dev/null +++ b/packages/react-native/ReactCommon/yoga/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/packages/react-native/ReactCommon/yoga/yoga/style/StyleValuePool.h b/packages/react-native/ReactCommon/yoga/yoga/style/StyleValuePool.h new file mode 100644 index 00000000000000..f8ee93e47b3f16 --- /dev/null +++ b/packages/react-native/ReactCommon/yoga/yoga/style/StyleValuePool.h @@ -0,0 +1,127 @@ +/* + * 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 + +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 { + assert( + handle.type() == StyleValueHandle::Type::Point || + handle.type() == StyleValueHandle::Type::Percent); + 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 { + assert(handle.type() == StyleValueHandle::Type::Number); + 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) { + constexpr uint16_t kValueSignMask = 0b0000'1000'0000'0000; + constexpr uint16_t kValueMagnitudeMask = 0b0000'0111'1111'1111; + const bool isNegative = (value & kValueSignMask) != 0; + return static_cast( + (value & kValueMagnitudeMask) * (isNegative ? -1 : 1)); + } + + SmallValueBuffer<4> buffer_; +}; + +} // namespace facebook::yoga