Skip to content

Commit

Permalink
yoga::bit_cast (#39358)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #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
  • Loading branch information
NickGerleman authored and facebook-github-bot committed Sep 8, 2023
1 parent 9dd654f commit 8658bdc
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "YogaJniException.h"

#include <yoga/Yoga-internal.h>
#include <yoga/bits/BitCast.h>

// TODO: Reconcile missing layoutContext functionality from callbacks in the C
// API and use that
Expand Down Expand Up @@ -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<float*>(&wBits);
const float* measuredHeight = reinterpret_cast<float*>(&hBits);
const float measuredWidth = yoga::bit_cast<float>(wBits);
const float measuredHeight = yoga::bit_cast<float>(hBits);

return YGSize{*measuredWidth, *measuredHeight};
return YGSize{measuredWidth, measuredHeight};
} else {
return YGSize{
widthMode == YGMeasureModeUndefined ? 0 : width,
Expand Down
29 changes: 29 additions & 0 deletions packages/react-native/ReactCommon/yoga/yoga/bits/BitCast.h
Original file line number Diff line number Diff line change
@@ -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 <cstring>
#include <type_traits>

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 <class To, class From>
std::enable_if_t<
sizeof(To) == sizeof(From) && std::is_trivially_copyable_v<From> &&
std::is_trivially_copyable_v<To> &&
std::is_trivially_constructible_v<To>,
To>
bit_cast(const From& src) noexcept {
To dst;
std::memcpy(&dst, &src, sizeof(To));
return dst;
}

} // namespace facebook::yoga
47 changes: 6 additions & 41 deletions packages/react-native/ReactCommon/yoga/yoga/style/CompactValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,7 @@
#include <yoga/YGMacros.h>
#include <yoga/YGValue.h>

#if defined(__has_include) && __has_include(<version>)
// needed to be able to evaluate defined(__cpp_lib_bit_cast)
#include <version>
#else
// needed to be able to evaluate defined(__cpp_lib_bit_cast)
#include <ciso646>
#endif

#ifdef __cpp_lib_bit_cast
#include <bit>
#else
#include <cstring>
#endif
#include <yoga/bits/BitCast.h>

static_assert(
std::numeric_limits<float>::is_iec559,
Expand Down Expand Up @@ -76,7 +64,7 @@ class YOGA_EXPORT CompactValue {
}

uint32_t unitBit = Unit == YGUnitPercent ? PERCENT_BIT : 0;
auto data = asU32(value);
auto data = yoga::bit_cast<uint32_t>(value);
data -= BIAS;
data |= unitBit;
return {data};
Expand Down Expand Up @@ -129,7 +117,7 @@ class YOGA_EXPORT CompactValue {
return YGValue{0.0f, YGUnitPercent};
}

if (std::isnan(asFloat(repr_))) {
if (std::isnan(yoga::bit_cast<float>(repr_))) {
return YGValueUndefined;
}

Expand All @@ -138,13 +126,14 @@ class YOGA_EXPORT CompactValue {
data += BIAS;

return YGValue{
asFloat(data), repr_ & 0x40000000 ? YGUnitPercent : YGUnitPoint};
yoga::bit_cast<float>(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<float>(repr_)));
}

bool isAuto() const noexcept { return repr_ == AUTO_BITS; }
Expand All @@ -164,30 +153,6 @@ class YOGA_EXPORT CompactValue {
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<uint32_t>(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<float>(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 <>
Expand Down

0 comments on commit 8658bdc

Please sign in to comment.