[libc] Replace type punning with bit_cast

Although type punning is defined for union in C, it is UB in C++.
This patch introduces a bit_cast function to convert between types in a safe way.

This is necessary to get llvm-libc compile with GCC.
This patch is extracted from D119002.

Differential Revision: https://reviews.llvm.org/D119145
This commit is contained in:
Guillaume Chatelet 2022-02-07 15:40:28 +00:00
parent 9be6e40d1a
commit 7e7ecef980
19 changed files with 129 additions and 60 deletions

View File

@ -0,0 +1,48 @@
//===-- Freestanding version of bit_cast -----------------------*- 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 LLVM_LIBC_SUPPORT_CPP_BIT_H
#define LLVM_LIBC_SUPPORT_CPP_BIT_H
namespace __llvm_libc {
#if defined __has_builtin
#if __has_builtin(__builtin_bit_cast)
#define LLVM_LIBC_HAS_BUILTIN_BIT_CAST
#endif
#endif
#if defined __has_builtin
#if __has_builtin(__builtin_memcpy_inline)
#define LLVM_LIBC_HAS_BUILTIN_MEMCPY_INLINE
#endif
#endif
// This function guarantees the bitcast to be optimized away by the compiler for
// GCC >= 8 and Clang >= 6.
template <class To, class From> constexpr To bit_cast(const From &from) {
static_assert(sizeof(To) == sizeof(From), "To and From must be of same size");
#if defined(LLVM_LIBC_HAS_BUILTIN_BIT_CAST)
return __builtin_bit_cast(To, from);
#else
To to;
char *dst = reinterpret_cast<char *>(&to);
const char *src = reinterpret_cast<const char *>(&from);
#if defined(LLVM_LIBC_HAS_BUILTIN_MEMCPY_INLINE)
__builtin_memcpy_inline(dst, src, sizeof(To));
#else
for (unsigned i = 0; i < sizeof(To); ++i)
dst[i] = src[i];
#endif // defined(LLVM_LIBC_HAS_BUILTIN_MEMCPY_INLINE)
return to;
#endif // defined(LLVM_LIBC_HAS_BUILTIN_BIT_CAST)
}
} // namespace __llvm_libc
#endif // LLVM_LIBC_SUPPORT_CPP_BIT_H

View File

@ -3,9 +3,10 @@ add_header_library(
HDRS
Array.h
ArrayRef.h
Bit.h
Bitset.h
Functional.h
Limits.h
StringView.h
TypeTraits.h
Limits.h
)

View File

@ -11,6 +11,7 @@
#include "PlatformDefs.h"
#include "src/__support/CPP/Bit.h"
#include "src/__support/CPP/TypeTraits.h"
#include "FloatProperties.h"
@ -35,7 +36,7 @@ template <typename T> struct ExponentWidth {
// floating numbers. On x86 platforms however, the 'long double' type maps to
// an x87 floating point format. This format is an IEEE 754 extension format.
// It is handled as an explicit specialization of this class.
template <typename T> union FPBits {
template <typename T> struct FPBits {
static_assert(cpp::IsFloatingPointType<T>::Value,
"FPBits instantiated with invalid type.");
@ -76,7 +77,6 @@ template <typename T> union FPBits {
bool get_sign() const {
return ((bits & FloatProp::SIGN_MASK) >> (FloatProp::BIT_WIDTH - 1));
}
T val;
static_assert(sizeof(T) == sizeof(UIntType),
"Data type and integral representation have different sizes.");
@ -96,15 +96,20 @@ template <typename T> union FPBits {
// type match.
template <typename XType,
cpp::EnableIfType<cpp::IsSame<T, XType>::Value, int> = 0>
explicit FPBits(XType x) : val(x) {}
constexpr explicit FPBits(XType x)
: bits(__llvm_libc::bit_cast<UIntType>(x)) {}
template <typename XType,
cpp::EnableIfType<cpp::IsSame<XType, UIntType>::Value, int> = 0>
explicit FPBits(XType x) : bits(x) {}
constexpr explicit FPBits(XType x) : bits(x) {}
FPBits() : bits(0) {}
explicit operator T() { return val; }
T get_val() const { return __llvm_libc::bit_cast<T>(bits); }
void set_val(T value) { bits = __llvm_libc::bit_cast<UIntType>(value); }
explicit operator T() const { return get_val(); }
UIntType uintval() const { return bits; }

View File

@ -12,6 +12,7 @@
#include "BasicOperations.h"
#include "FEnvImpl.h"
#include "FPBits.h"
#include "src/__support/CPP/Bit.h"
#include "src/__support/CPP/TypeTraits.h"
namespace __llvm_libc {
@ -285,7 +286,7 @@ static inline T hypot(T x, T y) {
}
y_new |= static_cast<UIntType>(out_exp) << MantissaWidth<T>::VALUE;
return *reinterpret_cast<T *>(&y_new);
return __llvm_libc::bit_cast<T>(y_new);
}
} // namespace fputil

View File

@ -14,6 +14,7 @@
#include "NormalFloat.h"
#include "PlatformDefs.h"
#include "src/__support/CPP/Bit.h"
#include "src/__support/CPP/TypeTraits.h"
#include <limits.h>
@ -171,7 +172,7 @@ static inline T nextafter(T from, T to) {
int_val = (to_bits.uintval() & sign_mask) + UIntType(1);
}
return *reinterpret_cast<T *>(&int_val);
return __llvm_libc::bit_cast<T>(int_val);
// TODO: Raise floating point exceptions as required by the standard.
}

View File

@ -10,6 +10,7 @@
#define LLVM_LIBC_SRC_SUPPORT_FPUTIL_GENERIC_SQRT_H
#include "sqrt_80_bit_long_double.h"
#include "src/__support/CPP/Bit.h"
#include "src/__support/CPP/TypeTraits.h"
#include "src/__support/FPUtil/FEnvImpl.h"
#include "src/__support/FPUtil/FPBits.h"
@ -203,7 +204,7 @@ sqrt(T x) {
break;
}
return *reinterpret_cast<T *>(&y);
return __llvm_libc::bit_cast<T>(y);
}
}
}

View File

@ -9,6 +9,7 @@
#ifndef LLVM_LIBC_SRC_SUPPORT_FPUTIL_X86_64_LONG_DOUBLE_BITS_H
#define LLVM_LIBC_SRC_SUPPORT_FPUTIL_X86_64_LONG_DOUBLE_BITS_H
#include "src/__support/CPP/Bit.h"
#include "src/__support/architectures.h"
#if !defined(LLVM_LIBC_ARCH_X86)
@ -30,7 +31,7 @@ template <> struct Padding<4> { static constexpr unsigned VALUE = 16; };
// x86_64 padding.
template <> struct Padding<8> { static constexpr unsigned VALUE = 48; };
template <> union FPBits<long double> {
template <> struct FPBits<long double> {
using UIntType = __uint128_t;
static constexpr int EXPONENT_BIAS = 0x3FFF;
@ -91,13 +92,11 @@ template <> union FPBits<long double> {
return ((bits & FloatProp::SIGN_MASK) >> (FloatProp::BIT_WIDTH - 1));
}
long double val;
FPBits() : bits(0) {}
template <typename XType,
cpp::EnableIfType<cpp::IsSame<long double, XType>::Value, int> = 0>
explicit FPBits(XType x) : val(x) {
explicit FPBits(XType x) : bits(__llvm_libc::bit_cast<UIntType>(x)) {
// bits starts uninitialized, and setting it to a long double only
// overwrites the first 80 bits. This clears those upper bits.
bits = bits & ((UIntType(1) << 80) - 1);
@ -107,7 +106,7 @@ template <> union FPBits<long double> {
cpp::EnableIfType<cpp::IsSame<XType, UIntType>::Value, int> = 0>
explicit FPBits(XType x) : bits(x) {}
operator long double() { return val; }
operator long double() { return __llvm_libc::bit_cast<long double>(bits); }
UIntType uintval() {
// We zero the padding bits as they can contain garbage.

View File

@ -15,6 +15,7 @@
#error "Invalid include"
#endif
#include "src/__support/CPP/Bit.h"
#include "src/__support/FPUtil/FPBits.h"
#include <stdint.h>
@ -111,7 +112,7 @@ static inline long double nextafter(long double from, long double to) {
}
}
return *reinterpret_cast<long double *>(&int_val);
return __llvm_libc::bit_cast<long double>(int_val);
// TODO: Raise floating point exceptions as required by the standard.
}

View File

@ -33,9 +33,8 @@ template <> inline double sqrt<double>(double x) {
}
template <> inline long double sqrt<long double>(long double x) {
long double result;
__asm__ __volatile__("fsqrt" : "=t"(result) : "t"(x));
return result;
__asm__ __volatile__("fsqrt" : "+t"(x));
return x;
}
} // namespace fputil

View File

@ -155,7 +155,7 @@ LLVM_LIBC_FUNCTION(float, log10f, (float x)) {
return x;
}
// Normalize denormal inputs.
xbits.val *= 0x1.0p23f;
xbits.set_val(xbits.get_val() * 0x1.0p23f);
m -= 23.0;
}
@ -164,7 +164,7 @@ LLVM_LIBC_FUNCTION(float, log10f, (float x)) {
xbits.set_unbiased_exponent(0x7F);
int f_index = xbits.get_mantissa() >> 16;
FPBits f(xbits.val);
FPBits f = xbits;
f.bits &= ~0x0000'FFFF;
double d = static_cast<float>(xbits) - static_cast<float>(f);

View File

@ -59,7 +59,7 @@ INLINE_FMA static inline float log(double x) {
int f_index =
xbits.get_mantissa() >> 45; // fputil::MantissaWidth<double>::VALUE - 7
FPBits f(xbits.val);
FPBits f = xbits;
// Clear the lowest 45 bits.
f.bits &= ~0x0000'1FFF'FFFF'FFFFULL;

View File

@ -138,7 +138,7 @@ LLVM_LIBC_FUNCTION(float, log2f, (float x)) {
return x;
}
// Normalize denormal inputs.
xbits.val *= 0x1.0p23f;
xbits.set_val(xbits.get_val() * 0x1.0p23f);
m = -23;
}
@ -149,7 +149,7 @@ LLVM_LIBC_FUNCTION(float, log2f, (float x)) {
// lookup tables.
int f_index = xbits.get_mantissa() >> 16;
FPBits f(xbits.val);
FPBits f = xbits;
// Clear the lowest 16 bits.
f.bits &= ~0x0000'FFFF;

View File

@ -104,7 +104,7 @@ LLVM_LIBC_FUNCTION(float, logf, (float x)) {
return x;
}
// Normalize denormal inputs.
xbits.val *= 0x1.0p23f;
xbits.set_val(xbits.get_val() * 0x1.0p23f);
m = -23;
}
@ -113,7 +113,7 @@ LLVM_LIBC_FUNCTION(float, logf, (float x)) {
xbits.set_unbiased_exponent(0x7F);
int f_index = xbits.get_mantissa() >> 16;
FPBits f(xbits.val);
FPBits f = xbits;
f.bits &= ~0x0000'FFFF;
double d = static_cast<float>(xbits) - static_cast<float>(f);

View File

@ -9,6 +9,7 @@
#ifndef LLVM_LIBC_SRC_MATH_MATH_UTILS_H
#define LLVM_LIBC_SRC_MATH_MATH_UTILS_H
#include "src/__support/CPP/Bit.h"
#include "src/__support/CPP/TypeTraits.h"
#include "src/__support/common.h"
#include <errno.h>
@ -19,19 +20,19 @@
namespace __llvm_libc {
static inline uint32_t as_uint32_bits(float x) {
return *reinterpret_cast<uint32_t *>(&x);
return __llvm_libc::bit_cast<uint32_t>(x);
}
static inline uint64_t as_uint64_bits(double x) {
return *reinterpret_cast<uint64_t *>(&x);
return __llvm_libc::bit_cast<uint64_t>(x);
}
static inline float as_float(uint32_t x) {
return *reinterpret_cast<float *>(&x);
return __llvm_libc::bit_cast<float>(x);
}
static inline double as_double(uint64_t x) {
return *reinterpret_cast<double *>(&x);
return __llvm_libc::bit_cast<double>(x);
}
static inline uint32_t top12_bits(float x) { return as_uint32_bits(x) >> 20; }

View File

@ -7,6 +7,8 @@ add_header_library(
memcmp_implementations.h
memcpy_implementations.h
memset_implementations.h
DEPS
standalone_cpp
)
add_header_library(

View File

@ -9,6 +9,7 @@
#ifndef LLVM_LIBC_SRC_STRING_MEMORY_UTILS_ELEMENTS_X86_H
#define LLVM_LIBC_SRC_STRING_MEMORY_UTILS_ELEMENTS_X86_H
#include "src/__support/CPP/Bit.h"
#include "src/__support/architectures.h"
#if defined(LLVM_LIBC_ARCH_X86)
@ -66,16 +67,18 @@ struct M128 {
using T = char __attribute__((__vector_size__(SIZE)));
static uint16_t mask(T value) {
// NOLINTNEXTLINE(llvmlibc-callee-namespace)
return _mm_movemask_epi8(value);
return _mm_movemask_epi8(__llvm_libc::bit_cast<__m128i>(value));
}
static uint16_t not_equal_mask(T a, T b) { return mask(a != b); }
static T load(const char *ptr) {
// NOLINTNEXTLINE(llvmlibc-callee-namespace)
return _mm_loadu_si128(reinterpret_cast<__m128i_u const *>(ptr));
return __llvm_libc::bit_cast<T>(
_mm_loadu_si128(reinterpret_cast<__m128i_u const *>(ptr)));
}
static void store(char *ptr, T value) {
// NOLINTNEXTLINE(llvmlibc-callee-namespace)
return _mm_storeu_si128(reinterpret_cast<__m128i_u *>(ptr), value);
return _mm_storeu_si128(reinterpret_cast<__m128i_u *>(ptr),
__llvm_libc::bit_cast<__m128i>(value));
}
static T get_splatted_value(const char v) {
const T splatted = {v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v};
@ -91,16 +94,18 @@ struct M256 {
using T = char __attribute__((__vector_size__(SIZE)));
static uint32_t mask(T value) {
// NOLINTNEXTLINE(llvmlibc-callee-namespace)
return _mm256_movemask_epi8(value);
return _mm256_movemask_epi8(__llvm_libc::bit_cast<__m256i>(value));
}
static uint32_t not_equal_mask(T a, T b) { return mask(a != b); }
static T load(const char *ptr) {
// NOLINTNEXTLINE(llvmlibc-callee-namespace)
return _mm256_loadu_si256(reinterpret_cast<__m256i const *>(ptr));
return __llvm_libc::bit_cast<T>(
_mm256_loadu_si256(reinterpret_cast<__m256i const *>(ptr)));
}
static void store(char *ptr, T value) {
// NOLINTNEXTLINE(llvmlibc-callee-namespace)
return _mm256_storeu_si256(reinterpret_cast<__m256i *>(ptr), value);
return _mm256_storeu_si256(reinterpret_cast<__m256i *>(ptr),
__llvm_libc::bit_cast<__m256i>(value));
}
static T get_splatted_value(const char v) {
const T splatted = {v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v,
@ -117,15 +122,16 @@ struct M512 {
using T = char __attribute__((__vector_size__(SIZE)));
static uint64_t not_equal_mask(T a, T b) {
// NOLINTNEXTLINE(llvmlibc-callee-namespace)
return _mm512_cmpneq_epi8_mask(a, b);
return _mm512_cmpneq_epi8_mask(__llvm_libc::bit_cast<__m512i>(a),
__llvm_libc::bit_cast<__m512i>(b));
}
static T load(const char *ptr) {
// NOLINTNEXTLINE(llvmlibc-callee-namespace)
return _mm512_loadu_epi8(ptr);
return __llvm_libc::bit_cast<T>(_mm512_loadu_epi8(ptr));
}
static void store(char *ptr, T value) {
// NOLINTNEXTLINE(llvmlibc-callee-namespace)
return _mm512_storeu_epi8(ptr, value);
return _mm512_storeu_epi8(ptr, __llvm_libc::bit_cast<__m512i>(value));
}
static T get_splatted_value(const char v) {
const T splatted = {v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v,

View File

@ -9,6 +9,7 @@
#ifndef LLVM_LIBC_TEST_SRC_MATH_NEXTAFTERTEST_H
#define LLVM_LIBC_TEST_SRC_MATH_NEXTAFTERTEST_H
#include "src/__support/CPP/Bit.h"
#include "src/__support/CPP/TypeTraits.h"
#include "src/__support/FPUtil/BasicOperations.h"
#include "src/__support/FPUtil/FPBits.h"
@ -51,54 +52,54 @@ public:
T x = zero;
T result = func(x, T(1));
UIntType expected_bits = 1;
T expected = *reinterpret_cast<T *>(&expected_bits);
T expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
result = func(x, T(-1));
expected_bits = (UIntType(1) << (BIT_WIDTH_OF_TYPE - 1)) + 1;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
x = neg_zero;
result = func(x, 1);
expected_bits = 1;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
result = func(x, -1);
expected_bits = (UIntType(1) << (BIT_WIDTH_OF_TYPE - 1)) + 1;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
// 'from' is max subnormal value.
x = *reinterpret_cast<const T *>(&max_subnormal);
x = __llvm_libc::bit_cast<T>(max_subnormal);
result = func(x, 1);
expected = *reinterpret_cast<const T *>(&min_normal);
expected = __llvm_libc::bit_cast<T>(min_normal);
ASSERT_FP_EQ(result, expected);
result = func(x, 0);
expected_bits = max_subnormal - 1;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
x = -x;
result = func(x, -1);
expected_bits = (UIntType(1) << (BIT_WIDTH_OF_TYPE - 1)) + min_normal;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
result = func(x, 0);
expected_bits =
(UIntType(1) << (BIT_WIDTH_OF_TYPE - 1)) + max_subnormal - 1;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
// 'from' is min subnormal value.
x = *reinterpret_cast<const T *>(&min_subnormal);
x = __llvm_libc::bit_cast<T>(min_subnormal);
result = func(x, 1);
expected_bits = min_subnormal + 1;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
ASSERT_FP_EQ(func(x, 0), 0);
@ -106,35 +107,35 @@ public:
result = func(x, -1);
expected_bits =
(UIntType(1) << (BIT_WIDTH_OF_TYPE - 1)) + min_subnormal + 1;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
ASSERT_FP_EQ(func(x, 0), T(-0.0));
// 'from' is min normal.
x = *reinterpret_cast<const T *>(&min_normal);
x = __llvm_libc::bit_cast<T>(min_normal);
result = func(x, 0);
expected_bits = max_subnormal;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
result = func(x, inf);
expected_bits = min_normal + 1;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
x = -x;
result = func(x, 0);
expected_bits = (UIntType(1) << (BIT_WIDTH_OF_TYPE - 1)) + max_subnormal;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
result = func(x, -inf);
expected_bits = (UIntType(1) << (BIT_WIDTH_OF_TYPE - 1)) + min_normal + 1;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
// 'from' is max normal and 'to' is infinity.
x = *reinterpret_cast<const T *>(&max_normal);
x = __llvm_libc::bit_cast<T>(max_normal);
result = func(x, inf);
ASSERT_FP_EQ(result, inf);
@ -145,14 +146,14 @@ public:
x = inf;
result = func(x, 0);
expected_bits = max_normal;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
ASSERT_FP_EQ(func(x, inf), inf);
x = neg_inf;
result = func(x, 0);
expected_bits = (UIntType(1) << (BIT_WIDTH_OF_TYPE - 1)) + max_normal;
expected = *reinterpret_cast<T *>(&expected_bits);
expected = __llvm_libc::bit_cast<T>(expected_bits);
ASSERT_FP_EQ(result, expected);
ASSERT_FP_EQ(func(x, neg_inf), neg_inf);

View File

@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//
#include "src/__support/CPP/Bit.h"
#include "utils/MPFRWrapper/MPFRUtils.h"
#include "utils/UnitTest/FPMatcher.h"
#include "utils/UnitTest/Test.h"
@ -47,7 +48,7 @@ public:
constexpr UIntType COUNT = 1'000'001;
constexpr UIntType STEP = HIDDEN_BIT / COUNT;
for (UIntType i = 0, v = 0; i <= COUNT; ++i, v += STEP) {
T x = *reinterpret_cast<T *>(&v);
T x = __llvm_libc::bit_cast<T>(v);
test_all_rounding_modes(func, x);
}
}
@ -56,7 +57,7 @@ public:
constexpr UIntType COUNT = 10'000'001;
constexpr UIntType STEP = UIntType(-1) / COUNT;
for (UIntType i = 0, v = 0; i <= COUNT; ++i, v += STEP) {
T x = *reinterpret_cast<T *>(&v);
T x = __llvm_libc::bit_cast<T>(v);
if (isnan(x) || (x < 0)) {
continue;
}

View File

@ -39,6 +39,7 @@ cc_library(
hdrs = [
"src/__support/CPP/Array.h",
"src/__support/CPP/ArrayRef.h",
"src/__support/CPP/Bit.h",
"src/__support/CPP/Bitset.h",
"src/__support/CPP/Functional.h",
"src/__support/CPP/Limits.h",
@ -661,6 +662,7 @@ cc_library(
],
deps = [
":__support_common",
":__support_standalone_cpp",
":libc_root",
],
)