diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -298,6 +298,8 @@ def note_constexpr_bit_cast_indet_dest : Note< "indeterminate value can only initialize an object of type 'unsigned char'" "%select{, 'char',|}1 or 'std::byte'; %0 is invalid">; +def note_constexpr_bit_cast_unrepresentable_value : Note< + "value %1 cannot be represented in type %0">; def note_constexpr_pseudo_destructor : Note< "pseudo-destructor call is not permitted in constant expressions " "until C++20">; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -6627,9 +6627,15 @@ } bool visitInt(const APSInt &Val, QualType Ty, CharUnits Offset) { - CharUnits Width = Info.Ctx.getTypeSizeInChars(Ty); - SmallVector Bytes(Width.getQuantity()); - llvm::StoreIntToMemory(Val, &*Bytes.begin(), Width.getQuantity()); + APSInt AdjustedVal = Val; + unsigned Width = AdjustedVal.getBitWidth(); + if (Ty->isBooleanType()) { + Width = Info.Ctx.getTypeSize(Ty); + AdjustedVal = AdjustedVal.extend(Width); + } + + SmallVector Bytes(Width / 8); + llvm::StoreIntToMemory(AdjustedVal, &*Bytes.begin(), Width / 8); Buffer.writeObject(Offset, Bytes); return true; } @@ -6670,6 +6676,13 @@ return None; } + llvm::NoneType unrepresentableValue(QualType Ty, const APSInt &Val) { + Info.FFDiag(BCE->getBeginLoc(), + diag::note_constexpr_bit_cast_unrepresentable_value) + << Ty << Val.toString(/*Radix=*/10); + return None; + } + Optional visit(const BuiltinType *T, CharUnits Offset, const EnumType *EnumSugar = nullptr) { if (T->isNullPtrType()) { @@ -6680,6 +6693,20 @@ } CharUnits SizeOf = Info.Ctx.getTypeSizeInChars(T); + + // Work around floating point types that contain unused padding bytes. This + // is really just `long double` on x86, which is the only fundamental type + // with padding bytes. + if (T->isRealFloatingType()) { + const llvm::fltSemantics &Semantics = + Info.Ctx.getFloatTypeSemantics(QualType(T, 0)); + unsigned NumBits = llvm::APFloatBase::getSizeInBits(Semantics); + assert(NumBits % 8 == 0); + CharUnits NumBytes = CharUnits::fromQuantity(NumBits / 8); + if (NumBytes != SizeOf) + SizeOf = NumBytes; + } + SmallVector Bytes; if (!Buffer.readObject(Offset, SizeOf, Bytes)) { // If this is std::byte or unsigned char, then its okay to store an @@ -6704,6 +6731,15 @@ if (T->isIntegralOrEnumerationType()) { Val.setIsSigned(T->isSignedIntegerOrEnumerationType()); + + unsigned IntWidth = Info.Ctx.getIntWidth(QualType(T, 0)); + if (IntWidth != Val.getBitWidth()) { + APSInt Truncated = Val.trunc(IntWidth); + if (Truncated.extend(Val.getBitWidth()) != Val) + return unrepresentableValue(QualType(T, 0), Val); + Val = Truncated; + } + return APValue(Val); } diff --git a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp --- a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp +++ b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp @@ -23,6 +23,10 @@ template constexpr To bit_cast(const From &from) { static_assert(sizeof(To) == sizeof(From)); + // expected-note@+9 {{cannot be represented in type 'bool'}} +#ifdef __x86_64 + // expected-note@+7 {{or 'std::byte'; '__int128' is invalid}} +#endif #ifdef __CHAR_UNSIGNED__ // expected-note@+4 2 {{indeterminate value can only initialize an object of type 'unsigned char', 'char', or 'std::byte'; 'signed char' is invalid}} #else @@ -397,3 +401,65 @@ }; constexpr IdentityInUnion identity3a = {42}; constexpr unsigned char identity3b = __builtin_bit_cast(unsigned char, identity3a.n); + +namespace test_bool { + +constexpr bool test_bad_bool = bit_cast('A'); // expected-error {{must be initialized by a constant expression}} expected-note{{in call}} + +static_assert(round_trip(true), ""); +static_assert(round_trip(false), ""); +static_assert(round_trip(false), ""); + +static_assert(round_trip((char)0), ""); +static_assert(round_trip((char)1), ""); +} + +namespace test_long_double { +#ifdef __x86_64 +constexpr __int128_t test_cast_to_int128 = bit_cast<__int128_t>((long double)0); // expected-error{{must be initialized by a constant expression}} expected-note{{in call}} + +constexpr long double ld = 3.1425926539; + +struct bytes { + unsigned char d[16]; +}; + +static_assert(round_trip(ld), ""); + +static_assert(round_trip(10.0L)); + +constexpr bool f(bool read_uninit) { + bytes b = bit_cast(ld); + unsigned char ld_bytes[10] = { + 0x0, 0x48, 0x9f, 0x49, 0xf0, + 0x3c, 0x20, 0xc9, 0x0, 0x40, + }; + + for (int i = 0; i != 10; ++i) + if (ld_bytes[i] != b.d[i]) + return false; + + if (read_uninit && b.d[10]) // expected-note{{read of uninitialized object is not allowed in a constant expression}} + return false; + + return true; +} + +static_assert(f(/*read_uninit=*/false), ""); +static_assert(f(/*read_uninit=*/true), ""); // expected-error{{static_assert expression is not an integral constant expression}} expected-note{{in call to 'f(true)'}} + +constexpr bytes ld539 = { + 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0xc0, 0x86, + 0x8, 0x40, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, +}; + +constexpr long double fivehundredandthirtynine = 539.0; + +static_assert(bit_cast(ld539) == fivehundredandthirtynine, ""); + +#else +static_assert(round_trip<__int128_t>(34.0L)); +#endif +}