From 26d2a2682f2527c32392b3c0691e73de7612f282 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Fri, 8 Sep 2023 13:03:48 -0700 Subject: [PATCH] yoga::bit_cast Summary: X-link: https://github.com/facebook/react-native/pull/39358 This adds a function polyfilling C++ 20's `std::bit_cast`, using `memcpy()` to be safe with strict aliasing rules. This replaces the conditional code in CompactValue for type punning, an unsafe place in YGJNI where we do it unsafely, and is used in ValuePool. The polyfill can be switched to `std::bit_cast` whenever we adopt C++ 20. Note that this doesn't actually call into `memcpy()`, as verified by Godbolt. Compilers are aware of the memcpy type punning pattern and optimize it, but it's ugly and confusing to folks who haven't seen it before. Reviewed By: javache Differential Revision: D49082997 fbshipit-source-id: b848775a68286bdb11b2a3a95bef8069364ac9b5 --- java/jni/YGJNIVanilla.cpp | 8 +++---- yoga/bits/BitCast.h | 29 ++++++++++++++++++++++++ yoga/style/CompactValue.h | 47 +++++---------------------------------- 3 files changed, 39 insertions(+), 45 deletions(-) create mode 100644 yoga/bits/BitCast.h diff --git a/java/jni/YGJNIVanilla.cpp b/java/jni/YGJNIVanilla.cpp index 46d2248a..6a1406c1 100644 --- a/java/jni/YGJNIVanilla.cpp +++ b/java/jni/YGJNIVanilla.cpp @@ -16,6 +16,7 @@ #include "YogaJniException.h" #include +#include // TODO: Reconcile missing layoutContext functionality from callbacks in the C // API and use that @@ -677,11 +678,10 @@ static YGSize YGJNIMeasureFunc( uint32_t wBits = 0xFFFFFFFF & (measureResult >> 32); uint32_t hBits = 0xFFFFFFFF & measureResult; - // TODO: this is unsafe under strict aliasing and should use bit_cast - const float* measuredWidth = reinterpret_cast(&wBits); - const float* measuredHeight = reinterpret_cast(&hBits); + const float measuredWidth = yoga::bit_cast(wBits); + const float measuredHeight = yoga::bit_cast(hBits); - return YGSize{*measuredWidth, *measuredHeight}; + return YGSize{measuredWidth, measuredHeight}; } else { return YGSize{ widthMode == YGMeasureModeUndefined ? 0 : width, diff --git a/yoga/bits/BitCast.h b/yoga/bits/BitCast.h new file mode 100644 index 00000000..eaca348c --- /dev/null +++ b/yoga/bits/BitCast.h @@ -0,0 +1,29 @@ +/* + * 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 + +namespace facebook::yoga { + +// Polyfill for std::bit_cast() from C++20, to allow safe type punning +// https://en.cppreference.com/w/cpp/numeric/bit_cast +template +std::enable_if_t< + sizeof(To) == sizeof(From) && std::is_trivially_copyable_v && + std::is_trivially_copyable_v && + std::is_trivially_constructible_v, + To> +bit_cast(const From& src) noexcept { + To dst; + std::memcpy(&dst, &src, sizeof(To)); + return dst; +} + +} // namespace facebook::yoga diff --git a/yoga/style/CompactValue.h b/yoga/style/CompactValue.h index 498b984a..282a518f 100644 --- a/yoga/style/CompactValue.h +++ b/yoga/style/CompactValue.h @@ -14,19 +14,7 @@ #include #include -#if defined(__has_include) && __has_include() -// needed to be able to evaluate defined(__cpp_lib_bit_cast) -#include -#else -// needed to be able to evaluate defined(__cpp_lib_bit_cast) -#include -#endif - -#ifdef __cpp_lib_bit_cast -#include -#else -#include -#endif +#include static_assert( std::numeric_limits::is_iec559, @@ -76,7 +64,7 @@ public: } uint32_t unitBit = Unit == YGUnitPercent ? PERCENT_BIT : 0; - auto data = asU32(value); + auto data = yoga::bit_cast(value); data -= BIAS; data |= unitBit; return {data}; @@ -129,7 +117,7 @@ public: return YGValue{0.0f, YGUnitPercent}; } - if (std::isnan(asFloat(repr_))) { + if (std::isnan(yoga::bit_cast(repr_))) { return YGValueUndefined; } @@ -138,13 +126,14 @@ public: data += BIAS; return YGValue{ - asFloat(data), repr_ & 0x40000000 ? YGUnitPercent : YGUnitPoint}; + yoga::bit_cast(data), + repr_ & 0x40000000 ? YGUnitPercent : YGUnitPoint}; } bool isUndefined() const noexcept { return ( repr_ != AUTO_BITS && repr_ != ZERO_BITS_POINT && - repr_ != ZERO_BITS_PERCENT && std::isnan(asFloat(repr_))); + repr_ != ZERO_BITS_PERCENT && std::isnan(yoga::bit_cast(repr_))); } bool isAuto() const noexcept { return repr_ == AUTO_BITS; } @@ -164,30 +153,6 @@ private: constexpr CompactValue(uint32_t data) noexcept : repr_(data) {} VISIBLE_FOR_TESTING uint32_t repr() { return repr_; } - - static uint32_t asU32(float f) { -#ifdef __cpp_lib_bit_cast - return std::bit_cast(f); -#else - uint32_t u; - static_assert( - sizeof(u) == sizeof(f), "uint32_t and float must have the same size"); - std::memcpy(&u, &f, sizeof(f)); - return u; -#endif - } - - static float asFloat(uint32_t u) { -#ifdef __cpp_lib_bit_cast - return std::bit_cast(u); -#else - float f; - static_assert( - sizeof(f) == sizeof(u), "uint32_t and float must have the same size"); - std::memcpy(&f, &u, sizeof(u)); - return f; -#endif - } }; template <>