diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst --- a/libcxx/docs/FeatureTestMacroTable.rst +++ b/libcxx/docs/FeatureTestMacroTable.rst @@ -190,7 +190,7 @@ ------------------------------------------------- ----------------- ``__cpp_lib_bind_front`` ``201907L`` ------------------------------------------------- ----------------- - ``__cpp_lib_bit_cast`` *unimplemented* + ``__cpp_lib_bit_cast`` ``201806L`` ------------------------------------------------- ----------------- ``__cpp_lib_bitops`` *unimplemented* ------------------------------------------------- ----------------- diff --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv --- a/libcxx/docs/Status/Cxx20Papers.csv +++ b/libcxx/docs/Status/Cxx20Papers.csv @@ -29,7 +29,7 @@ "`P0019R8 `__","LWG","Atomic Ref","Rapperswil","","" "`P0458R2 `__","LWG","Checking for Existence of an Element in Associative Containers","Rapperswil","|Complete|","13.0" "`P0475R1 `__","LWG","LWG 2511: guaranteed copy elision for piecewise construction","Rapperswil","|Complete|","" -"`P0476R2 `__","LWG","Bit-casting object representations","Rapperswil","","" +"`P0476R2 `__","LWG","Bit-casting object representations","Rapperswil","|Complete|","14.0" "`P0528R3 `__","CWG","The Curious Case of Padding Bits, Featuring Atomic Compare-and-Exchange","Rapperswil","","" "`P0542R5 `__","CWG","Support for contract based programming in C++","Rapperswil","*Removed in Cologne*","n/a" "`P0556R3 `__","LWG","Integral power-of-2 operations","Rapperswil","|Complete|","9.0" diff --git a/libcxx/docs/UsingLibcxx.rst b/libcxx/docs/UsingLibcxx.rst --- a/libcxx/docs/UsingLibcxx.rst +++ b/libcxx/docs/UsingLibcxx.rst @@ -338,6 +338,7 @@ * ``upper_bound`` * ``lock_guard``'s constructors * ``as_const`` +* ``bit_cast`` * ``forward`` * ``move`` * ``move_if_noexcept`` diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -94,6 +94,7 @@ __algorithm/upper_bound.h __availability __bit_reference + __bit/bit_cast.h __bits __bsd_locale_defaults.h __bsd_locale_fallbacks.h diff --git a/libcxx/include/__bit/bit_cast.h b/libcxx/include/__bit/bit_cast.h new file mode 100644 --- /dev/null +++ b/libcxx/include/__bit/bit_cast.h @@ -0,0 +1,38 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___BIT_BIT_CAST_H +#define _LIBCPP___BIT_BIT_CAST_H + +#include <__config> +#include + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +#pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER > 17 + +template && + is_trivially_copyable_v<_FromType> +>> +_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI +constexpr _ToType bit_cast(_FromType const& __from) noexcept { + return __builtin_bit_cast(_ToType, __from); +} + +#endif // _LIBCPP_STD_VER > 17 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___BIT_BIT_CAST_H diff --git a/libcxx/include/bit b/libcxx/include/bit --- a/libcxx/include/bit +++ b/libcxx/include/bit @@ -14,6 +14,9 @@ bit synopsis namespace std { + // [bit.cast], bit_cast + template + constexpr To bit_cast(const From& from) noexcept; // C++20 // [bit.pow.two], integral powers of 2 template @@ -54,8 +57,9 @@ */ -#include <__config> +#include <__bit/bit_cast.h> #include <__bits> // __libcpp_clz +#include <__config> #include <__debug> #include #include diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -335,6 +335,10 @@ module bit { header "bit" export * + + module __bit { + module bit_cast { private header "__bit/bit_cast.h" } + } } module bitset { header "bitset" diff --git a/libcxx/include/version b/libcxx/include/version --- a/libcxx/include/version +++ b/libcxx/include/version @@ -286,7 +286,7 @@ # define __cpp_lib_barrier 201907L # endif # define __cpp_lib_bind_front 201907L -// # define __cpp_lib_bit_cast 201806L +# define __cpp_lib_bit_cast 201806L // # define __cpp_lib_bitops 201907L # define __cpp_lib_bounded_array_traits 201902L # if !defined(_LIBCPP_HAS_NO_CHAR8_T) diff --git a/libcxx/test/libcxx/diagnostics/detail.headers/bit/bit_cast.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/bit/bit_cast.module.verify.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/diagnostics/detail.headers/bit/bit_cast.module.verify.cpp @@ -0,0 +1,16 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: modules-build + +// WARNING: This test was generated by 'generate_private_header_tests.py' +// and should not be edited manually. + +// expected-error@*:* {{use of private header from outside its module: '__bit/bit_cast.h'}} +#include <__bit/bit_cast.h> diff --git a/libcxx/test/libcxx/diagnostics/nodiscard_extensions.pass.cpp b/libcxx/test/libcxx/diagnostics/nodiscard_extensions.pass.cpp --- a/libcxx/test/libcxx/diagnostics/nodiscard_extensions.pass.cpp +++ b/libcxx/test/libcxx/diagnostics/nodiscard_extensions.pass.cpp @@ -18,6 +18,7 @@ // be listed in `UsingLibcxx.rst` in the documentation for the extension. #include +#include // bit_cast #include // to_integer #include // identity #include @@ -165,13 +166,13 @@ void test_nontemplate_cast_wrappers() { -#if TEST_STD_VER >= 17 +#if TEST_STD_VER > 14 std::byte b{42}; std::to_integer(b); #endif -#if TEST_STD_VER >= 20 - // std::bit_cast(42); +#if TEST_STD_VER > 17 + std::bit_cast(42); #endif #if TEST_STD_VER > 20 diff --git a/libcxx/test/libcxx/diagnostics/nodiscard_extensions.verify.cpp b/libcxx/test/libcxx/diagnostics/nodiscard_extensions.verify.cpp --- a/libcxx/test/libcxx/diagnostics/nodiscard_extensions.verify.cpp +++ b/libcxx/test/libcxx/diagnostics/nodiscard_extensions.verify.cpp @@ -19,6 +19,7 @@ // ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_NODISCARD #include +#include // bit_cast #include // to_integer #include // identity #include @@ -323,14 +324,15 @@ void test_nontemplate_cast_wrappers() { -#if TEST_STD_VER >= 17 +#if TEST_STD_VER > 14 std::byte b{42}; // expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}} std::to_integer(b); #endif -#if TEST_STD_VER >= 20 - // std::bit_cast(42); +#if TEST_STD_VER > 17 + // expected-warning-re@+1 {{ignoring return value of function declared with {{'nodiscard'|warn_unused_result}} attribute}} + std::bit_cast(42); #endif #if TEST_STD_VER > 20 diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.pass.cpp --- a/libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.pass.cpp @@ -81,17 +81,11 @@ #elif TEST_STD_VER == 20 -# if !defined(_LIBCPP_VERSION) -# ifndef __cpp_lib_bit_cast -# error "__cpp_lib_bit_cast should be defined in c++20" -# endif -# if __cpp_lib_bit_cast != 201806L -# error "__cpp_lib_bit_cast should have the value 201806L in c++20" -# endif -# else // _LIBCPP_VERSION -# ifdef __cpp_lib_bit_cast -# error "__cpp_lib_bit_cast should not be defined because it is unimplemented in libc++!" -# endif +# ifndef __cpp_lib_bit_cast +# error "__cpp_lib_bit_cast should be defined in c++20" +# endif +# if __cpp_lib_bit_cast != 201806L +# error "__cpp_lib_bit_cast should have the value 201806L in c++20" # endif # if !defined(_LIBCPP_VERSION) @@ -123,17 +117,11 @@ #elif TEST_STD_VER > 20 -# if !defined(_LIBCPP_VERSION) -# ifndef __cpp_lib_bit_cast -# error "__cpp_lib_bit_cast should be defined in c++2b" -# endif -# if __cpp_lib_bit_cast != 201806L -# error "__cpp_lib_bit_cast should have the value 201806L in c++2b" -# endif -# else // _LIBCPP_VERSION -# ifdef __cpp_lib_bit_cast -# error "__cpp_lib_bit_cast should not be defined because it is unimplemented in libc++!" -# endif +# ifndef __cpp_lib_bit_cast +# error "__cpp_lib_bit_cast should be defined in c++2b" +# endif +# if __cpp_lib_bit_cast != 201806L +# error "__cpp_lib_bit_cast should have the value 201806L in c++2b" # endif # if !defined(_LIBCPP_VERSION) diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp --- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp @@ -2264,17 +2264,11 @@ # error "__cpp_lib_bind_front should have the value 201907L in c++20" # endif -# if !defined(_LIBCPP_VERSION) -# ifndef __cpp_lib_bit_cast -# error "__cpp_lib_bit_cast should be defined in c++20" -# endif -# if __cpp_lib_bit_cast != 201806L -# error "__cpp_lib_bit_cast should have the value 201806L in c++20" -# endif -# else // _LIBCPP_VERSION -# ifdef __cpp_lib_bit_cast -# error "__cpp_lib_bit_cast should not be defined because it is unimplemented in libc++!" -# endif +# ifndef __cpp_lib_bit_cast +# error "__cpp_lib_bit_cast should be defined in c++20" +# endif +# if __cpp_lib_bit_cast != 201806L +# error "__cpp_lib_bit_cast should have the value 201806L in c++20" # endif # if !defined(_LIBCPP_VERSION) @@ -3421,17 +3415,11 @@ # error "__cpp_lib_bind_front should have the value 201907L in c++2b" # endif -# if !defined(_LIBCPP_VERSION) -# ifndef __cpp_lib_bit_cast -# error "__cpp_lib_bit_cast should be defined in c++2b" -# endif -# if __cpp_lib_bit_cast != 201806L -# error "__cpp_lib_bit_cast should have the value 201806L in c++2b" -# endif -# else // _LIBCPP_VERSION -# ifdef __cpp_lib_bit_cast -# error "__cpp_lib_bit_cast should not be defined because it is unimplemented in libc++!" -# endif +# ifndef __cpp_lib_bit_cast +# error "__cpp_lib_bit_cast should be defined in c++2b" +# endif +# if __cpp_lib_bit_cast != 201806L +# error "__cpp_lib_bit_cast should have the value 201806L in c++2b" # endif # if !defined(_LIBCPP_VERSION) diff --git a/libcxx/test/std/numerics/bit/bit.cast/bit_cast.compile.pass.cpp b/libcxx/test/std/numerics/bit/bit.cast/bit_cast.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/numerics/bit/bit.cast/bit_cast.compile.pass.cpp @@ -0,0 +1,63 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-no-concepts + +// +// +// template +// constexpr To bit_cast(const From& from) noexcept; // C++20 + +// This test makes sure that std::bit_cast fails when any of the following +// constraints are violated: +// +// (1.1) sizeof(To) == sizeof(From) is true; +// (1.2) is_trivially_copyable_v is true; +// (1.3) is_trivially_copyable_v is true. +// +// Also check that it's ill-formed when the return type would be +// ill-formed, even though that is not explicitly mentioned in the +// specification (but it can be inferred from the synopsis). + +#include +#include + +template +concept bit_cast_is_valid = requires(From from) { + { std::bit_cast(from) } -> std::same_as; +}; + +// Types are not the same size +namespace ns1 { + struct To { char a; }; + struct From { char a; char b; }; + static_assert(!bit_cast_is_valid); + static_assert(!bit_cast_is_valid); +} + +// To is not trivially copyable +namespace ns2 { + struct To { char a; To(To const&); }; + struct From { char a; }; + static_assert(!bit_cast_is_valid); +} + +// From is not trivially copyable +namespace ns3 { + struct To { char a; }; + struct From { char a; From(From const&); }; + static_assert(!bit_cast_is_valid); +} + +// The return type is ill-formed +namespace ns4 { + struct From { char a; char b; }; + static_assert(!bit_cast_is_valid); + static_assert(!bit_cast_is_valid); +} diff --git a/libcxx/test/std/numerics/bit/bit.cast/bit_cast.pass.cpp b/libcxx/test/std/numerics/bit/bit.cast/bit_cast.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/numerics/bit/bit.cast/bit_cast.pass.cpp @@ -0,0 +1,263 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +// +// +// template +// constexpr To bit_cast(const From& from) noexcept; // C++20 + +#include +#include +#include +#include +#include +#include +#include + +// std::bit_cast does not preserve padding bits, so if T has padding bits, +// the results might not memcmp cleanly. +template +void test_roundtrip_through_buffer(T from) { + struct Buffer { char buffer[sizeof(T)]; }; + Buffer middle = std::bit_cast(from); + T to = std::bit_cast(middle); + Buffer middle2 = std::bit_cast(to); + + assert((from == to) == (from == from)); // because NaN + + if constexpr (HasUniqueObjectRepresentations) { + assert(std::memcmp(&from, &middle, sizeof(T)) == 0); + assert(std::memcmp(&to, &middle, sizeof(T)) == 0); + assert(std::memcmp(&middle, &middle2, sizeof(T)) == 0); + } +} + +template +void test_roundtrip_through_nested_T(T from) { + struct Nested { T x; }; + static_assert(sizeof(Nested) == sizeof(T)); + + Nested middle = std::bit_cast(from); + T to = std::bit_cast(middle); + Nested middle2 = std::bit_cast(to); + + assert((from == to) == (from == from)); // because NaN + + if constexpr (HasUniqueObjectRepresentations) { + assert(std::memcmp(&from, &middle, sizeof(T)) == 0); + assert(std::memcmp(&to, &middle, sizeof(T)) == 0); + assert(std::memcmp(&middle, &middle2, sizeof(T)) == 0); + } +} + +template +void test_roundtrip_through(T from) { + static_assert(sizeof(Intermediate) == sizeof(T)); + + Intermediate middle = std::bit_cast(from); + T to = std::bit_cast(middle); + Intermediate middle2 = std::bit_cast(to); + + assert((from == to) == (from == from)); // because NaN + + if constexpr (HasUniqueObjectRepresentations) { + assert(std::memcmp(&from, &middle, sizeof(T)) == 0); + assert(std::memcmp(&to, &middle, sizeof(T)) == 0); + assert(std::memcmp(&middle, &middle2, sizeof(T)) == 0); + } +} + +template +constexpr std::array generate_signed_integral_values() { + return {std::numeric_limits::min(), + std::numeric_limits::min() + 1, + static_cast(-2), static_cast(-1), + static_cast(0), static_cast(1), + static_cast(2), static_cast(3), + std::numeric_limits::max() - 1, + std::numeric_limits::max()}; +} + +template +constexpr std::array generate_unsigned_integral_values() { + return {static_cast(0), static_cast(1), + static_cast(2), static_cast(3), + std::numeric_limits::max() - 1, + std::numeric_limits::max()}; +} + +bool tests() { + for (bool b : {false, true}) { + test_roundtrip_through_nested_T(b); + test_roundtrip_through_buffer(b); + test_roundtrip_through(b); + } + + for (char c : {'\0', 'a', 'b', 'c', 'd'}) { + test_roundtrip_through_nested_T(c); + test_roundtrip_through_buffer(c); + } + + // Fundamental signed integer types + for (signed char i : generate_signed_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + } + + for (short i : generate_signed_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + } + + for (int i : generate_signed_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + test_roundtrip_through(i); + } + + for (long i : generate_signed_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + } + + for (long long i : generate_signed_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + test_roundtrip_through(i); + } + + // Fundamental unsigned integer types + for (unsigned char i : generate_unsigned_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + } + + for (unsigned short i : generate_unsigned_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + } + + for (unsigned int i : generate_unsigned_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + test_roundtrip_through(i); + } + + for (unsigned long i : generate_unsigned_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + } + + for (unsigned long long i : generate_unsigned_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + test_roundtrip_through(i); + } + + // Fixed width signed integer types + for (std::int32_t i : generate_signed_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + test_roundtrip_through(i); + test_roundtrip_through(i); + test_roundtrip_through(i); + } + + for (std::int64_t i : generate_signed_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + test_roundtrip_through(i); + test_roundtrip_through(i); + test_roundtrip_through(i); + } + + // Fixed width unsigned integer types + for (std::uint32_t i : generate_unsigned_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + test_roundtrip_through(i); + test_roundtrip_through(i); + test_roundtrip_through(i); + } + + for (std::uint64_t i : generate_unsigned_integral_values()) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + test_roundtrip_through(i); + test_roundtrip_through(i); + test_roundtrip_through(i); + } + + // Floating point types + for (float i : {0.0f, 1.0f, -1.0f, 10.0f, -10.0f, 1e10f, 1e-10f, 1e20f, 1e-20f, 2.71828f, 3.14159f, + std::nanf(""), + __builtin_nanf("0x55550001"), // NaN with a payload + std::numeric_limits::signaling_NaN(), + std::numeric_limits::quiet_NaN()}) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + test_roundtrip_through(i); + } + + for (double i : {0.0, 1.0, -1.0, 10.0, -10.0, 1e10, 1e-10, 1e100, 1e-100, + 2.718281828459045, + 3.141592653589793238462643383279502884197169399375105820974944, + std::nan(""), + std::numeric_limits::signaling_NaN(), + std::numeric_limits::quiet_NaN()}) { + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + test_roundtrip_through(i); + } + + for (long double i : {0.0l, 1.0l, -1.0l, 10.0l, -10.0l, 1e10l, 1e-10l, 1e100l, 1e-100l, + 2.718281828459045l, + 3.141592653589793238462643383279502884197169399375105820974944l, + std::nanl(""), + std::numeric_limits::signaling_NaN(), + std::numeric_limits::quiet_NaN()}) { + // Note that x86's `long double` has 80 value bits and 48 padding bits. + test_roundtrip_through_nested_T(i); + test_roundtrip_through_buffer(i); + + // On arm64 on Apple platforms, long double is just double, so we don't + // test against int128, but instead against double itself. Otherwise, + // we test against int128 if we have those types available. +#if defined(__aarch64__) && defined(__APPLE__) +# define LONG_DOUBLE_IS_DOUBLE +#endif + +#if defined(LONG_DOUBLE_IS_DOUBLE) + test_roundtrip_through(i); +#elif !defined(_LIBCPP_HAS_NO_INT128) + test_roundtrip_through<__int128_t, false>(i); + test_roundtrip_through<__uint128_t, false>(i); +#endif + } + + return true; +} + +// TODO: There doesn't seem to be a way to perform non-trivial correctness +// tests inside constexpr. +constexpr bool basic_constexpr_test() { + struct Nested { char buffer[sizeof(int)]; }; + int from = 3; + Nested middle = std::bit_cast(from); + int to = std::bit_cast(middle); + assert(from == to); + return true; +} + +int main(int, char**) { + tests(); + static_assert(basic_constexpr_test()); + return 0; +} diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -156,7 +156,6 @@ "name": "__cpp_lib_bit_cast", "values": { "c++20": 201806 }, "headers": ["bit"], - "unimplemented": True, }, { "name": "__cpp_lib_bitops", "values": { "c++20": 201907 },