diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index c81d17ed64108..7020f70f7c1b0 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -316,10 +316,14 @@ def note_constexpr_memcpy_unsupported : Note< "size to copy (%4) is not a multiple of size of element type %3 (%5)|" "source is not a contiguous array of at least %4 elements of type %3|" "destination is not a contiguous array of at least %4 elements of type %3}2">; +def note_constexpr_bit_cast_bad_bits : Note< + "bit_cast source expression (type %5) does not produce a constant value for " + "%select{bit|byte}0 [%1] (of {%2%plural{0:|:..0}2}) which are required by " + "target type %4 %select{|(subobject %3)}6">; def note_constexpr_bit_cast_unsupported_type : Note< "constexpr bit cast involving type %0 is not yet supported">; -def note_constexpr_bit_cast_unsupported_bitfield : Note< - "constexpr bit_cast involving bit-field is not yet supported">; +def note_constexpr_bit_cast_invalid_decl : Note< + "bit_cast here %select{from|to}0 invalid declaration %0">; def note_constexpr_bit_cast_invalid_type : Note< "bit_cast %select{from|to}0 a %select{|type with a }1" "%select{union|pointer|member pointer|volatile|reference}2 " diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 986302e1fd225..9d714ef09a133 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -38,7 +38,6 @@ #include "Interp/State.h" #include "clang/AST/APValue.h" #include "clang/AST/ASTContext.h" -#include "clang/AST/ASTDiagnostic.h" #include "clang/AST/ASTLambda.h" #include "clang/AST/Attr.h" #include "clang/AST/CXXInheritance.h" @@ -49,20 +48,36 @@ #include "clang/AST/OptionalDiagnostic.h" #include "clang/AST/RecordLayout.h" #include "clang/AST/StmtVisitor.h" +#include "clang/AST/Type.h" #include "clang/AST/TypeLoc.h" #include "clang/Basic/Builtins.h" -#include "clang/Basic/DiagnosticSema.h" +#include "clang/Basic/DiagnosticAST.h" #include "clang/Basic/TargetInfo.h" #include "llvm/ADT/APFixedPoint.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/APSInt.h" #include "llvm/ADT/SmallBitVector.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Compiler.h" #include "llvm/Support/Debug.h" +#include "llvm/Support/Format.h" #include "llvm/Support/SaveAndRestore.h" +#include "llvm/Support/SwapByteOrder.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/raw_ostream.h" +#include +#include +#include #include #include +#include +#include #include +#include #define DEBUG_TYPE "exprconstant" @@ -6901,51 +6916,260 @@ bool HandleOperatorDeleteCall(EvalInfo &Info, const CallExpr *E) { //===----------------------------------------------------------------------===// namespace { -class BitCastBuffer { - // FIXME: We're going to need bit-level granularity when we support - // bit-fields. - // FIXME: Its possible under the C++ standard for 'char' to not be 8 bits, but - // we don't support a host or target where that is the case. Still, we should - // use a more generic type in case we ever do. - SmallVector, 32> Bytes; +struct BitSlice : public std::slice { + BitSlice(size_t Offset, size_t Num) : std::slice(Offset, Num, 1){}; - static_assert(std::numeric_limits::digits >= 8, - "Need at least 8 bit unsigned char"); + inline size_t end() const { return start() + size(); } +}; - bool TargetIsLittleEndian; +struct BitFieldInfo { + /// The offset (in bits) within the appropriate storage type. + const unsigned Offset : 16; + + /// The size of the value held by the bit-field, in bits. + const unsigned Width : 15; + + /// Whether the bit-field is signed. + const unsigned IsSigned : 1; + + /// The storage size in bits which should be used when accessing this + /// bitfield. + const unsigned StorageSize; + + /// The offset of the bitfield storage from the start of the struct. + const CharUnits StorageOffset; + + static BitFieldInfo MakeInfo(const ASTContext &Ctx, const FieldDecl *FD, + const ASTRecordLayout &Layout) { + const unsigned StorageSize = Ctx.getTypeSize(FD->getType()), + FieldOffsetBits = Layout.getFieldOffset(FD->getFieldIndex()); + + unsigned Width = FD->getBitWidthValue(Ctx); + if (Width > StorageSize) { + // e.g. `unsigned uint8_t c : 12` + // we truncate to CHAR_BIT * sizeof(T) + // (the extra bits are padding) + Width = StorageSize; + } + unsigned Offset = FieldOffsetBits % StorageSize; + if (Ctx.getTargetInfo().isBigEndian()) { + // big endian bits count from MSB to LSB + // so a bit-field of width 16 and size 12 will + // occupy bits [0-11] on a little endian machine, + // but [3-15] on a big endian machine + Offset = StorageSize - (Offset + Width); + } + return { + Offset, + Width, + FD->getType()->isSignedIntegerOrEnumerationType(), + StorageSize, + Ctx.toCharUnitsFromBits((FieldOffsetBits / StorageSize) * StorageSize), + }; + } +}; -public: - BitCastBuffer(CharUnits Width, bool TargetIsLittleEndian) - : Bytes(Width.getQuantity()), - TargetIsLittleEndian(TargetIsLittleEndian) {} - - [[nodiscard]] bool readObject(CharUnits Offset, CharUnits Width, - SmallVectorImpl &Output) const { - for (CharUnits I = Offset, E = Offset + Width; I != E; ++I) { - // If a byte of an integer is uninitialized, then the whole integer is - // uninitialized. - if (!Bytes[I.getQuantity()]) +struct BitCastBuffer { + // The number of bits in a `char`, needed to handle endianness (which is + // assumed to be exclusively big or little) for values with more bits than + // this number. + // + // No current platforms support varying this size. + static const uint64_t CharBit = 8; + + const uint64_t BitWidth; + const bool IsNativeEndian; + + APInt Data; + APInt Invalid; // Indeterminate bits + + BitCastBuffer(uint64_t BitWidth, bool TargetIsLittleEndian, uint64_t CharBit) + : BitWidth(BitWidth), + IsNativeEndian(llvm::sys::IsLittleEndianHost == TargetIsLittleEndian), + Data(BitWidth, 0), Invalid(BitWidth, ~0, /* extend "sign" bit */ true) { + assert(Invalid.countl_one() == BitWidth); + assert(CharBit == BitCastBuffer::CharBit); + } + + [[nodiscard]] bool readMasked(const uint64_t Offset, APInt &Output, + const APInt &Mask) const { + assert(Output.getBitWidth() == Mask.getBitWidth()); + const BitSlice Which = {Offset, Output.getBitWidth()}; + + const auto read = [&](const APInt &Mask) { + if ((getBits(Invalid, Which) & Mask) != 0) return false; - Output.push_back(*Bytes[I.getQuantity()]); + + Output = (Output & ~Mask) | (getBits(Data, Which) & Mask); + return true; + }; + + if (!IsNativeEndian && Output.getBitWidth() > CharBit) { + bool OK = read(Mask.byteSwap()); + Output = Output.byteSwap(); + return OK; } - if (llvm::sys::IsLittleEndianHost != TargetIsLittleEndian) - std::reverse(Output.begin(), Output.end()); + + return read(Mask); + } + + [[nodiscard]] inline bool readObject(const uint64_t Offset, + APInt &Output) const { + return readObject({Offset, Output.getBitWidth()}, Output); + } + + [[nodiscard]] bool readObject(const BitSlice &Which, APInt &Output) const { + assert(Output.getBitWidth() <= BitWidth); + assert(Which.size() <= Output.getBitWidth()); + assert(Which.end() <= BitWidth); + assert((IsNativeEndian || withinByte(Which) || + APInt::getBitsSet(BitWidth, Which.start(), Which.end()) + .byteSwap() + .isShiftedMask()) && + "use readMasked instead"); + + if (getBits(Invalid, Which) != 0) + return false; + + copyBitsFrom(Output, {0, Which.size()}, Data, Which); + + if (!IsNativeEndian && Output.getBitWidth() > CharBit) + Output = Output.byteSwap(); + return true; } - void writeObject(CharUnits Offset, SmallVectorImpl &Input) { - if (llvm::sys::IsLittleEndianHost != TargetIsLittleEndian) - std::reverse(Input.begin(), Input.end()); + void writeMasked(const uint64_t Offset, const APInt &Input, + const APInt &Mask) { + assert(Input.getBitWidth() == Mask.getBitWidth()); + const uint64_t BW = Input.getBitWidth(); + const BitSlice Dest = {Offset, BW}; - size_t Index = 0; - for (unsigned char Byte : Input) { - assert(!Bytes[Offset.getQuantity() + Index] && "overwriting a byte?"); - Bytes[Offset.getQuantity() + Index] = Byte; - ++Index; + auto write = [&](const APInt &Input, const APInt &Mask) { + assert((~getBits(Invalid, Dest) & Mask) == 0 && "overwriting data?"); + const APInt Val = (Input & Mask) | (getBits(Data, Dest) & ~Mask); + const APInt Written = getBits(Invalid, Dest) ^ Mask; + + copyBitsFrom(Data, Dest, Val, {0, BW}); + copyBitsFrom(Invalid, Dest, Written, {0, BW}); + }; + + if (!IsNativeEndian && BW > CharBit) { + write(Input.byteSwap(), Mask.byteSwap()); + return; + } + write(Input, Mask); + } + + void writeObject(const uint64_t Offset, const APInt &Input) { + writeObject({Offset, Input.getBitWidth()}, Input, {0, Input.getBitWidth()}); + } + + void writeObject(const BitSlice &Dst, const APInt &Input, + const BitSlice &Src) { + assert(Src.size() == Dst.size()); + assert(Src.end() <= Input.getBitWidth()); + assert(Dst.end() <= BitWidth); + assert(~getBits(Invalid, Dst) == 0 && "overwriting data?"); + assert((IsNativeEndian || (withinByte(Src) && withinByte(Dst)) || + [&] { + unsigned lo, len; + return Src.size() % 8 == 0 && + APInt::getBitsSet(Src.size(), Src.start(), Src.end()) + .byteSwap() + .isShiftedMask(lo, len) && + lo == Src.start() && len == Src.size() && + Dst.start() % CharBit == 0 && Dst.size() % CharBit == 0; + }()) && + "use writeMasked instead"); + + auto write = [&](const APInt &Input) { + copyBitsFrom(Data, Dst, Input, Src); + clearBits(Invalid, Dst); + }; + + if (!IsNativeEndian && Input.getBitWidth() > CharBit) { + write(Input.byteSwap()); + return; } + + write(Input); + } + + // true iff the range described by Which from start (inclusive) to end + // (exclusive) refers to the same addressable byte, i.e. + // [0, 0) -> yes + // [0, 3) -> yes + // [0, 8) -> yes + // [16, 24) -> yes + // [123, 123) -> yes + // [7, 9) -> no + inline static bool withinByte(const BitSlice &Which) { + // NB: Which.start() may equal Which.end(), and either may be zero, so + // care must be taken here to avoid underflow + return Which.size() == 0 || + Which.start() / CharBit + 1 == (Which.end() + CharBit - 1) / CharBit; + } + + inline static APInt getBits(const APInt &Int, const BitSlice &Which) { + // more lenient than extractBits (which asserts `start < BitWidth`) + // this permits e.g. zero-width reads "one past the last bit" + assert(Which.end() <= Int.getBitWidth()); + if (Which.size() == 0) { + return APInt::getZeroWidth(); + } + return Int.extractBits(Which.size(), Which.start()); + } + + // copyBitsFrom acts like `LHS[Dst] = RHS[Src];`, if `APInt`s supported + // slicing + inline static void copyBitsFrom(APInt &LHS, const BitSlice &Dst, + const APInt &RHS, const BitSlice &Src) { + assert(Src.size() == Dst.size()); + + if (Src.start() > 0 || Src.end() < RHS.getBitWidth() || + RHS.getBitWidth() != Dst.size()) { + APInt Val = RHS.lshr(Src.start()).trunc(Src.size()).zext(Dst.size()); + LHS.insertBits(Val, Dst.start()); + return; + } + LHS.insertBits(RHS, Dst.start()); + } + + inline static void clearBits(APInt &Int, const BitSlice &Which) { + unsigned Bit = Which.start(), Rem = Which.size() % 64; + if (Rem > 0) // else APInt crashes when Bit == 0 + Int.insertBits(0ull, Bit, Rem); + Bit += Rem; + for (unsigned End = Which.end(); Bit < End; Bit += 64) + Int.insertBits(0ull, Bit, 64u); + } + + static llvm::FormattedBytes formatInt(const APInt &Int) { + // implicit in the below we're assuming that CHAR_BIT is 8. + // + // this might get confusing on a PDP-10, where "bytes" were a software + // abstraction that varied in size (potentially even within the same + // program; see https://retrocomputing.stackexchange.com/a/15514). + // + // happily, this is a dump method, so we get to do non-backwards-compatible + // things like assume the programmer will know if they're in the extremely + // unlikely context where "byte" means something other than 8 bits. + const auto *Data = Int.getRawData(); + const auto NumBytes = Int.getBitWidth() / 8; + assert(NumBytes <= Int.getNumWords() * sizeof(*Data)); + const ArrayRef AsBytes(reinterpret_cast(Data), + NumBytes); + const unsigned int NumPerLine = 40, ByteGroupSize = 1; + + return format_bytes(AsBytes, std::nullopt, NumPerLine, ByteGroupSize); } - size_t size() { return Bytes.size(); } + LLVM_DUMP_METHOD void dump() { + llvm::dbgs() << "BitCastBuffer{Bytes: [" << formatInt(Data) + << "], Invalid: [" << formatInt(Invalid) << "] (=> Valid: [" + << formatInt(~Invalid) << "])}\n"; + } }; /// Traverse an APValue to produce an BitCastBuffer, emulating how the current @@ -6957,8 +7181,9 @@ class APValueToBufferConverter { APValueToBufferConverter(EvalInfo &Info, CharUnits ObjectWidth, const CastExpr *BCE) - : Info(Info), - Buffer(ObjectWidth, Info.Ctx.getTargetInfo().isLittleEndian()), + : Info(Info), Buffer(Info.Ctx.toBits(ObjectWidth), + Info.Ctx.getTargetInfo().isLittleEndian(), + Info.Ctx.getCharWidth()), BCE(BCE) {} bool visit(const APValue &Val, QualType Ty) { @@ -6967,13 +7192,13 @@ class APValueToBufferConverter { // Write out Val with type Ty into Buffer starting at Offset. bool visit(const APValue &Val, QualType Ty, CharUnits Offset) { - assert((size_t)Offset.getQuantity() <= Buffer.size()); + assert((size_t)Info.Ctx.toBits(Offset) <= Buffer.BitWidth); // As a special case, nullptr_t has an indeterminate value. if (Ty->isNullPtrType()) return true; - // Dig through Src to find the byte at SrcOffset. + // Dig through Val to find the bits. switch (Val.getKind()) { case APValue::Indeterminate: case APValue::None: @@ -7012,6 +7237,9 @@ class APValueToBufferConverter { bool visitRecord(const APValue &Val, QualType Ty, CharUnits Offset) { const RecordDecl *RD = Ty->getAsRecordDecl(); + if (RD->isInvalidDecl()) { + return invalidDecl(Ty); + } const ASTRecordLayout &Layout = Info.Ctx.getASTRecordLayout(RD); // Visit the base classes. @@ -7028,12 +7256,11 @@ class APValueToBufferConverter { // Visit the fields. unsigned FieldIdx = 0; - for (FieldDecl *FD : RD->fields()) { - if (FD->isBitField()) { - Info.FFDiag(BCE->getBeginLoc(), - diag::note_constexpr_bit_cast_unsupported_bitfield); - return false; - } + for (auto I = RD->field_begin(), E = RD->field_end(); I != E; + I++, FieldIdx++) { + FieldDecl *FD = *I; + if (FD->isBitField()) + continue; // see below uint64_t FieldOffsetBits = Layout.getFieldOffset(FieldIdx); @@ -7044,7 +7271,52 @@ class APValueToBufferConverter { QualType FieldTy = FD->getType(); if (!visit(Val.getStructField(FieldIdx), FieldTy, FieldOffset)) return false; - ++FieldIdx; + } + + // Handle bit-fields + FieldIdx = 0; + for (auto I = RD->field_begin(), E = RD->field_end(); I != E; + I++, FieldIdx++) { + FieldDecl *FD = *I; + if (!FD->isBitField()) + continue; + + // unnamed bit fields are purely padding + if (FD->isUnnamedBitfield()) + continue; + + auto FieldVal = Val.getStructField(FieldIdx); + if (!FieldVal.hasValue()) + continue; + + const auto BF = BitFieldInfo::MakeInfo(Info.Ctx, FD, Layout); + + APSInt BoolVal; + const APSInt &Val = [&]() -> const APSInt & { + const APSInt &Val = FieldVal.getInt(); + if (FD->getType()->isBooleanType()) { + // Let's zero extend the `i1` to be the full `Width` bits + // Note: this works because we refuse to read boolean + // values that don't have their high bits zeroed; see comment + // in BufferToAPValueConverter::visit(BuiltinType, ...) + BoolVal = Val.extend(BF.Width); + return BoolVal; + } + return Val; + }(); + assert(Val.getBitWidth() >= BF.Width); + if (!Buffer.IsNativeEndian && Val.getBitWidth() > 8) { + APInt AdjVal = (Val << BF.Offset); + APInt Mask = APInt::getBitsSet(Val.getBitWidth(), BF.Offset, + BF.Width + BF.Offset); + + Buffer.writeMasked(Info.Ctx.toBits(Offset + BF.StorageOffset), AdjVal, + Mask); + } else { + const uint64_t BitOffset = Info.Ctx.toBits(Offset + BF.StorageOffset); + Buffer.writeObject({BitOffset + BF.Offset, BF.Width}, Val, + {0, BF.Width}); + } } return true; @@ -7110,13 +7382,13 @@ class APValueToBufferConverter { if (VTy->isExtVectorBoolType()) { // Special handling for OpenCL bool vectors: - // Since these vectors are stored as packed bits, but we can't write - // individual bits to the BitCastBuffer, we'll buffer all of the elements - // together into an appropriately sized APInt and write them all out at - // once. Because we don't accept vectors where NElts * EltSize isn't a - // multiple of the char size, there will be no padding space, so we don't - // have to worry about writing data which should have been left - // uninitialized. + // Since these vectors are stored in memory as packed bits, but the + // constexpr interpreter stores them as individual 1-bit-wide APInts, we + // pack them together into a single appropriately sized APInt and write + // them all out at once. Because we don't accept vectors where NElts * + // EltSize isn't a multiple of the char size, there will be no padding + // space, so we don't have to worry about writing data which should have + // been left uninitialized. bool BigEndian = Info.Ctx.getTargetInfo().isBigEndian(); llvm::APInt Res = llvm::APInt::getZero(NElts); @@ -7125,12 +7397,10 @@ class APValueToBufferConverter { assert(EltAsInt.isUnsigned() && EltAsInt.getBitWidth() == 1 && "bool vector element must be 1-bit unsigned integer!"); - Res.insertBits(EltAsInt, BigEndian ? (NElts - I - 1) : I); + Res.setBitVal(BigEndian ? (NElts - I - 1) : I, EltAsInt[0]); } - SmallVector Bytes(NElts / 8); - llvm::StoreIntToMemory(Res, &*Bytes.begin(), NElts / 8); - Buffer.writeObject(Offset, Bytes); + Buffer.writeObject(Info.Ctx.toBits(Offset), Res); } else { // Iterate over each of the elements and write them out to the buffer at // the appropriate offset. @@ -7145,16 +7415,39 @@ class APValueToBufferConverter { } bool visitInt(const APSInt &Val, QualType Ty, CharUnits Offset) { - APSInt AdjustedVal = Val; - unsigned Width = AdjustedVal.getBitWidth(); if (Ty->isBooleanType()) { - Width = Info.Ctx.getTypeSize(Ty); - AdjustedVal = AdjustedVal.extend(Width); + // Let's zero extend the `i1` to be the full 8 bits + // Note: this works because we refuse to read boolean + // values that don't have their high bits zeroed; see comment + // in BufferToAPValueConverter::visit(BuiltinType, ...) + unsigned Width = Info.Ctx.getTypeSize(Ty); + Buffer.writeObject(Info.Ctx.toBits(Offset), Val.zext(Width)); + return true; } - SmallVector Bytes(Width / 8); - llvm::StoreIntToMemory(AdjustedVal, &*Bytes.begin(), Width / 8); - Buffer.writeObject(Offset, Bytes); + if (Ty->isBitIntType()) { + // This preserves the existing behavior that used to function like so: + // ```c++ + // SmallVector Bytes(Width / 8); + // llvm::StoreIntToMemory(AdjustedVal, &*Bytes.begin(), Width / 8); + // Buffer.writeObject(Offset, Bytes); + // ``` + // which, when provided a _BitInt(N), would write N/8 bytes (when it + // didn't crash) + const CharUnits Bytes = + CharUnits::fromQuantity(Info.Ctx.getIntWidth(Ty) / 8); + const unsigned Size = Info.Ctx.toBits(Bytes); + if (!Buffer.IsNativeEndian && Val.getBitWidth() > 8) { + Buffer.writeObject(Info.Ctx.toBits(Offset), Val.trunc(Size)); + } else { + Buffer.writeObject({static_cast(Info.Ctx.toBits(Offset)), Size}, + Val, {0, Size}); + } + + return true; + } + + Buffer.writeObject(Info.Ctx.toBits(Offset), Val); return true; } @@ -7163,6 +7456,12 @@ class APValueToBufferConverter { return visitInt(AsInt, Ty, Offset); } + bool invalidDecl(QualType Ty) { + Info.FFDiag(BCE->getBeginLoc(), diag::note_constexpr_bit_cast_invalid_decl) + << /* checking dest */ false << Ty; + return false; + } + public: static std::optional convert(EvalInfo &Info, const APValue &Src, const CastExpr *BCE) { @@ -7194,6 +7493,12 @@ class BufferToAPValueConverter { return std::nullopt; } + std::nullopt_t invalidDecl(QualType Ty) { + Info.FFDiag(BCE->getBeginLoc(), diag::note_constexpr_bit_cast_invalid_decl) + << /* checking dest */ true << Ty; + return std::nullopt; + } + std::nullopt_t unrepresentableValue(QualType Ty, const APSInt &Val) { Info.FFDiag(BCE->getBeginLoc(), diag::note_constexpr_bit_cast_unrepresentable_value) @@ -7201,6 +7506,85 @@ class BufferToAPValueConverter { return std::nullopt; } + std::nullopt_t badBits(QualType Ty, const BitSlice &Want) { + Info.FFDiag(BCE->getExprLoc(), diag::note_constexpr_bit_cast_indet_dest, 1) + << Ty << Info.Ctx.getLangOpts().CharIsSigned; + uint64_t BitWidth = Info.Ctx.getTypeSize(BCE->getType()); + uint64_t ByteWidth = + Info.Ctx.getTypeSizeInChars(BCE->getType()).getQuantity(); + assert(ByteWidth == Buffer.BitWidth / Info.Ctx.getCharWidth()); + + uint64_t BW = Info.Ctx.getTypeSize(Ty); + APInt Indet = Buffer.Invalid.lshr(Want.start()).trunc(BW); + auto Mask = APInt::getBitsSet(BW, 0, Want.size()); + if (!Buffer.IsNativeEndian && BW % 16 == 0) { + Indet = Indet.byteSwap(); + Mask = Mask.byteSwap(); + } + + auto ByteAligned = true; + + APInt Missing = Indet & Mask; + assert(!Missing.isZero() && "bad bits called with no bad bits?"); + llvm::SmallVector> MissingBitRanges; + int NextBit = Want.start(); + while (!Missing.isZero()) { + APInt Last(Missing); + int N = Missing.countr_zero(); + + Missing.lshrInPlace(N); + auto M = Missing.countr_one(); + + MissingBitRanges.push_back({NextBit + N, NextBit + N + M}); + + Missing.lshrInPlace(M); + NextBit += N; + NextBit += M; + ByteAligned &= N % Info.Ctx.getCharWidth() == 0; + ByteAligned &= M % Info.Ctx.getCharWidth() == 0; + } + + llvm::SmallString<32> RangesStr; + llvm::raw_svector_ostream OS(RangesStr); + bool First = true; + for (auto [Start, End] : MissingBitRanges) { + if (!First) + OS << " "; + else + First = false; + if (ByteAligned) { + Start /= Info.Ctx.getCharWidth(); + End /= Info.Ctx.getCharWidth(); + } + size_t Len = End - Start; + if (Len > 1) { + OS << Start << "-" << End - 1; + } else { + OS << Start; + } + } + + assert(RangesStr.size() > 0); + auto LastIdx = (ByteAligned ? ByteWidth : BitWidth) - 1; + bool IsForSubobject = + BCE->getType().getCanonicalType() != Ty.getCanonicalType(); + Info.Note(BCE->getSubExpr()->getExprLoc(), + diag::note_constexpr_bit_cast_bad_bits) + << ByteAligned << RangesStr << LastIdx << Ty << BCE->getType() + << BCE->getSubExpr()->getType() << IsForSubobject; + return std::nullopt; + } + + static bool canStoreIndeterminate(const Type *T, const EnumType *EnumSugar) { + // If this is std::byte or unsigned char, then its okay to store an + // indeterminate value. + bool IsStdByte = EnumSugar && EnumSugar->isStdByteType(); + bool IsUChar = + !EnumSugar && (T->isSpecificBuiltinType(BuiltinType::UChar) || + T->isSpecificBuiltinType(BuiltinType::Char_U)); + return IsStdByte || IsUChar; + } + std::optional visit(const BuiltinType *T, CharUnits Offset, const EnumType *EnumSugar = nullptr) { if (T->isNullPtrType()) { @@ -7210,52 +7594,60 @@ class BufferToAPValueConverter { APValue::NoLValuePath{}, /*IsNullPtr=*/true); } - CharUnits SizeOf = Info.Ctx.getTypeSizeInChars(T); + uint64_t SizeOf = Info.Ctx.getTypeSize(T); - // Work around floating point types that contain unused padding bytes. This + // Some floating point types contain unused padding bytes. This // is really just `long double` on x86, which is the only fundamental type - // with padding bytes. + // with padding bytes. (other than `bool`s, which are handled specially + // below) 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 - // indeterminate value. - bool IsStdByte = EnumSugar && EnumSugar->isStdByteType(); - bool IsUChar = - !EnumSugar && (T->isSpecificBuiltinType(BuiltinType::UChar) || - T->isSpecificBuiltinType(BuiltinType::Char_U)); - if (!IsStdByte && !IsUChar) { + if (NumBits != SizeOf) + SizeOf = NumBits; + } + + uint64_t BitAddr = Info.Ctx.toBits(Offset); + APSInt Val(SizeOf, true); + if (!Buffer.readObject(BitAddr, Val)) { + if (!canStoreIndeterminate(T, EnumSugar)) { QualType DisplayType(EnumSugar ? (const Type *)EnumSugar : T, 0); - Info.FFDiag(BCE->getExprLoc(), - diag::note_constexpr_bit_cast_indet_dest) - << DisplayType << Info.Ctx.getLangOpts().CharIsSigned; - return std::nullopt; + return badBits(DisplayType, {BitAddr, Val.getBitWidth()}); } return APValue::IndeterminateValue(); } - APSInt Val(SizeOf.getQuantity() * Info.Ctx.getCharWidth(), true); - llvm::LoadIntFromMemory(Val, &*Bytes.begin(), Bytes.size()); - 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) + if (T->isBooleanType()) { + // booleans are special in that they have natural padding. However, + // rather than treating the padding bits as such, we instead choose to + // see them more like "tag" bits that are architecturally required to be + // zeroed, i.e. invoking the "no value of type `To` corresponding to the + // representation" undefined behavior clause, and therefore refusing to + // produce a constant value. + // + // We do this because on write, we'd like to zero-extend a `bool` out to + // 8 bits so that it's possible to `bit_cast(false)` without + // additional ceremony. However, that means that if we permit any + // non-zero bit patterns to be cast _to_ a bool here, we'd permit a + // construct like the following: + // ```c++ + // bit_cast(bit_cast('\x02')) + // ``` + // to produce a constant `0x0` (because we'll zero-extend the LSB). + // + // Note that this is different behavior than we'll want for _BitInt(N) + // types, where we have no desire for a bit cast from a `_BitInt(3)` to + // produce a constant value for the other bits. + if (Val.getActiveBits() > 1) return unrepresentableValue(QualType(T, 0), Val); - Val = Truncated; + + Val = Val.trunc(1); } return APValue(Val); @@ -7272,6 +7664,9 @@ class BufferToAPValueConverter { std::optional visit(const RecordType *RTy, CharUnits Offset) { const RecordDecl *RD = RTy->getAsRecordDecl(); + if (RD->isInvalidDecl()) { + return invalidDecl(QualType(RD->getTypeForDecl(), 0)); + } const ASTRecordLayout &Layout = Info.Ctx.getASTRecordLayout(RD); unsigned NumBases = 0; @@ -7300,14 +7695,11 @@ class BufferToAPValueConverter { // Visit the fields. unsigned FieldIdx = 0; - for (FieldDecl *FD : RD->fields()) { - // FIXME: We don't currently support bit-fields. A lot of the logic for - // this is in CodeGen, so we need to factor it around. - if (FD->isBitField()) { - Info.FFDiag(BCE->getBeginLoc(), - diag::note_constexpr_bit_cast_unsupported_bitfield); - return std::nullopt; - } + for (auto I = RD->field_begin(), E = RD->field_end(); I != E; + I++, FieldIdx++) { + FieldDecl *FD = *I; + if (FD->isBitField()) + continue; // see below uint64_t FieldOffsetBits = Layout.getFieldOffset(FieldIdx); assert(FieldOffsetBits % Info.Ctx.getCharWidth() == 0); @@ -7320,7 +7712,89 @@ class BufferToAPValueConverter { if (!SubObj) return std::nullopt; ResultVal.getStructField(FieldIdx) = *SubObj; - ++FieldIdx; + } + + // Handle bit-fields + FieldIdx = 0; + for (auto I = RD->field_begin(), E = RD->field_end(); I != E; + I++, FieldIdx++) { + FieldDecl *FD = *I; + if (!FD->isBitField()) + continue; + + // matches the existing behavior + if (FD->getType()->isBitIntType()) + return unsupportedType(FD->getType()); + + // unnamed bit fields are purely padding + if (FD->isUnnamedBitfield()) + continue; + + const auto BF = BitFieldInfo::MakeInfo(Info.Ctx, FD, Layout); + const bool isUnsigned = + FD->getType()->isUnsignedIntegerOrEnumerationType(); + APSInt Val; + bool ReadOK; + const unsigned BitAddr = + Info.Ctx.toBits(Offset + BF.StorageOffset) + BF.Offset; + if (!Buffer.IsNativeEndian && BF.StorageSize > 8) { + Val = APSInt(BF.StorageSize, true); + const APInt Mask = + APInt::getBitsSet(BF.StorageSize, BF.Offset, BF.Offset + BF.Width); + + ReadOK = Buffer.readMasked(Info.Ctx.toBits(Offset + BF.StorageOffset), + Val, Mask); + + Val >>= BF.Offset; + Val = Val.trunc(BF.Width); + Val.setIsUnsigned(isUnsigned); + } else { + Val = APSInt(BF.Width, isUnsigned); + ReadOK = Buffer.readObject(BitAddr, Val); + } + + if (!ReadOK) { + const Type *T = FD->getType().getCanonicalType().getTypePtr(); + const EnumType *EnumSugar = dyn_cast(T); + if (!canStoreIndeterminate(T, EnumSugar)) { + QualType DisplayType(EnumSugar ? (const Type *)EnumSugar : T, 0); + return badBits(DisplayType, {BitAddr, BF.Width}); + } + + ResultVal.getStructField(FieldIdx) = APValue::IndeterminateValue(); + continue; + } + + if (FD->getType()->isBooleanType()) { + // booleans are special in that they have natural padding. However, + // rather than treating the padding bits as such, we instead choose to + // see them more like "tag" bits that are architecturally required to be + // zeroed, i.e. invoking the "no value of type `To` corresponding to the + // representation" undefined behavior clause, and therefore refusing to + // produce a constant value. + // + // We do this because on write, we'd like to zero-extend a `bool` out to + // 8 bits so that it's possible to `bit_cast(false)` without + // additional ceremony. However, that means that if we permit any + // non-zero bit patterns to be cast _to_ a bool here, we'd permit a + // construct like the following: + // ```c++ + // bit_cast(bit_cast('\x02')) + // ``` + // to produce a constant `0x0` (because we'll zero-extend the LSB). + // + // Note that this is different behavior than we'll want for _BitInt(N) + // types, where we have no desire for a bit cast from a `_BitInt(3)` to + // produce a constant value for the other bits. + if (Val.getActiveBits() > 1) + return unrepresentableValue(FD->getType(), Val); + + Val = Val.trunc(1); + } else { + Val = Val.extend(BF.StorageSize); + } + + ResultVal.getStructField(FieldIdx) = APValue(Val); } return ResultVal; @@ -7385,26 +7859,22 @@ class BufferToAPValueConverter { Elts.reserve(NElts); if (VTy->isExtVectorBoolType()) { // Special handling for OpenCL bool vectors: - // Since these vectors are stored as packed bits, but we can't read - // individual bits from the BitCastBuffer, we'll buffer all of the - // elements together into an appropriately sized APInt and write them all - // out at once. Because we don't accept vectors where NElts * EltSize - // isn't a multiple of the char size, there will be no padding space, so - // we don't have to worry about reading any padding data which didn't - // actually need to be accessed. + // Since these vectors are stored in memory as packed bits, but the + // constexpr interpreter wants to store them as individual 1-bit-wide + // APInts, so we unpack them here. Because we don't accept vectors where + // NElts * EltSize isn't a multiple of the char size, there will be no + // padding space, so we don't have to worry about reading any padding data + // which didn't actually need to be accessed. bool BigEndian = Info.Ctx.getTargetInfo().isBigEndian(); - SmallVector Bytes; - Bytes.reserve(NElts / 8); - if (!Buffer.readObject(Offset, CharUnits::fromQuantity(NElts / 8), Bytes)) - return std::nullopt; - - APSInt SValInt(NElts, true); - llvm::LoadIntFromMemory(SValInt, &*Bytes.begin(), Bytes.size()); + const unsigned BitAddr = Info.Ctx.toBits(Offset); + APSInt Val(NElts, true); + if (!Buffer.readObject(BitAddr, Val)) + return badBits(QualType(VTy, 0), {BitAddr, Val.getBitWidth()}); for (unsigned I = 0; I < NElts; ++I) { llvm::APInt Elt = - SValInt.extractBits(1, (BigEndian ? NElts - I - 1 : I) * EltSize); + Val.extractBits(1, (BigEndian ? NElts - I - 1 : I) * EltSize); Elts.emplace_back( APSInt(std::move(Elt), !EltTy->isSignedIntegerType())); } diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index c6218a491aece..e3d46c3140741 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -17219,6 +17219,13 @@ static bool ConvertAPValueToString(const APValue &V, QualType T, OS << "i)"; } break; + case APValue::ValueKind::Array: + case APValue::ValueKind::Vector: + case APValue::ValueKind::Struct: { + llvm::raw_svector_ostream OS(Str); + V.printPretty(OS, Context, T); + } break; + default: return false; } @@ -17256,11 +17263,10 @@ static bool UsefulToPrintExpr(const Expr *E) { /// Try to print more useful information about a failed static_assert /// with expression \E void Sema::DiagnoseStaticAssertDetails(const Expr *E) { - if (const auto *Op = dyn_cast(E); - Op && Op->getOpcode() != BO_LOr) { - const Expr *LHS = Op->getLHS()->IgnoreParenImpCasts(); - const Expr *RHS = Op->getRHS()->IgnoreParenImpCasts(); - + const auto Diagnose = [&](const Expr *LHS, const Expr *RHS, + const llvm::StringRef &OpStr) { + LHS = LHS->IgnoreParenImpCasts(); + RHS = RHS->IgnoreParenImpCasts(); // Ignore comparisons of boolean expressions with a boolean literal. if ((isa(LHS) && RHS->getType()->isBooleanType()) || (isa(RHS) && LHS->getType()->isBooleanType())) @@ -17287,10 +17293,19 @@ void Sema::DiagnoseStaticAssertDetails(const Expr *E) { DiagSide[I].ValueString, Context); } if (DiagSide[0].Print && DiagSide[1].Print) { - Diag(Op->getExprLoc(), diag::note_expr_evaluates_to) - << DiagSide[0].ValueString << Op->getOpcodeStr() - << DiagSide[1].ValueString << Op->getSourceRange(); + Diag(E->getExprLoc(), diag::note_expr_evaluates_to) + << DiagSide[0].ValueString << OpStr << DiagSide[1].ValueString + << E->getSourceRange(); } + }; + + if (const auto *Op = dyn_cast(E); + Op && Op->getOpcode() != BO_LOr) { + Diagnose(Op->getLHS(), Op->getRHS(), Op->getOpcodeStr()); + } else if (const auto *Op = dyn_cast(E); + Op && Op->isInfixBinaryOp()) { + Diagnose(Op->getArg(0), Op->getArg(1), + getOperatorSpelling(Op->getOperator())); } } diff --git a/clang/test/CXX/class/class.compare/class.eq/p3.cpp b/clang/test/CXX/class/class.compare/class.eq/p3.cpp index 04db022fe7302..53c4dda133301 100644 --- a/clang/test/CXX/class/class.compare/class.eq/p3.cpp +++ b/clang/test/CXX/class/class.compare/class.eq/p3.cpp @@ -6,11 +6,11 @@ struct A { }; static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 4, 5}); -static_assert(A{1, 2, 3, 4, 5} == A{0, 2, 3, 4, 5}); // expected-error {{failed}} -static_assert(A{1, 2, 3, 4, 5} == A{1, 0, 3, 4, 5}); // expected-error {{failed}} -static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 0, 4, 5}); // expected-error {{failed}} -static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 0, 5}); // expected-error {{failed}} -static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 4, 0}); // expected-error {{failed}} +static_assert(A{1, 2, 3, 4, 5} == A{0, 2, 3, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(A{1, 2, 3, 4, 5} == A{1, 0, 3, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 0, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 0, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 4, 0}); // expected-error {{failed}} expected-note {{evaluates to}} struct B { int a, b[3], c; @@ -18,8 +18,8 @@ struct B { }; static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 4, 5}); -static_assert(B{1, 2, 3, 4, 5} == B{0, 2, 3, 4, 5}); // expected-error {{failed}} -static_assert(B{1, 2, 3, 4, 5} == B{1, 0, 3, 4, 5}); // expected-error {{failed}} -static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 0, 4, 5}); // expected-error {{failed}} -static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 0, 5}); // expected-error {{failed}} -static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 4, 0}); // expected-error {{failed}} +static_assert(B{1, 2, 3, 4, 5} == B{0, 2, 3, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(B{1, 2, 3, 4, 5} == B{1, 0, 3, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 0, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 0, 5}); // expected-error {{failed}} expected-note {{evaluates to}} +static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 4, 0}); // expected-error {{failed}} expected-note {{evaluates to}} diff --git a/clang/test/CXX/class/class.compare/class.rel/p2.cpp b/clang/test/CXX/class/class.compare/class.rel/p2.cpp index 90115284d2bd0..07501c6a08184 100644 --- a/clang/test/CXX/class/class.compare/class.rel/p2.cpp +++ b/clang/test/CXX/class/class.compare/class.rel/p2.cpp @@ -10,15 +10,15 @@ namespace Rel { friend bool operator>=(const A&, const A&) = default; }; static_assert(A{0} < A{1}); - static_assert(A{1} < A{1}); // expected-error {{failed}} + static_assert(A{1} < A{1}); // expected-error {{failed}} expected-note {{'{1} < {1}'}} static_assert(A{0} <= A{1}); static_assert(A{1} <= A{1}); - static_assert(A{2} <= A{1}); // expected-error {{failed}} + static_assert(A{2} <= A{1}); // expected-error {{failed}} expected-note {{'{2} <= {1}'}} static_assert(A{1} > A{0}); - static_assert(A{1} > A{1}); // expected-error {{failed}} + static_assert(A{1} > A{1}); // expected-error {{failed}} expected-note {{'{1} > {1}'}} static_assert(A{1} >= A{0}); static_assert(A{1} >= A{1}); - static_assert(A{1} >= A{2}); // expected-error {{failed}} + static_assert(A{1} >= A{2}); // expected-error {{failed}} expected-note {{'{1} >= {2}'}} struct B { bool operator<=>(B) const = delete; // expected-note 4{{deleted here}} expected-note-re 8{{candidate {{.*}} deleted}} @@ -49,7 +49,7 @@ namespace NotEqual { friend bool operator!=(const A&, const A&) = default; }; static_assert(A{1} != A{2}); - static_assert(A{1} != A{1}); // expected-error {{failed}} + static_assert(A{1} != A{1}); // expected-error {{failed}} expected-note {{'{1} != {1}'}} struct B { bool operator==(B) const = delete; // expected-note {{deleted here}} expected-note-re 2{{candidate {{.*}} deleted}} diff --git a/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp index 95d6a55aee66a..8f31e8947a768 100644 --- a/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp +++ b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp @@ -33,7 +33,7 @@ struct Y {}; constexpr bool operator==(X x, Y) { return x.equal; } static_assert(X{true} == Y{}); -static_assert(X{false} == Y{}); // expected-error {{failed}} +static_assert(X{false} == Y{}); // expected-error {{failed}} expected-note{{'{false} == {}'}} // x == y -> y == x static_assert(Y{} == X{true}); diff --git a/clang/test/SemaCXX/constexpr-builtin-bit-cast-bitint.cpp b/clang/test/SemaCXX/constexpr-builtin-bit-cast-bitint.cpp new file mode 100644 index 0000000000000..3acf82dc0fb4a --- /dev/null +++ b/clang/test/SemaCXX/constexpr-builtin-bit-cast-bitint.cpp @@ -0,0 +1,82 @@ +// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -triple x86_64-apple-macosx10.14.0 %s +// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -triple x86_64-apple-macosx10.14.0 %s -fno-signed-char +// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -triple aarch64_be-linux-gnu %s + +// This is separate from constexpr-builtin-bit-cast.cpp because clangd17 seems to behave +// poorly around __BitInt(N) types, and this isolates that unfortunate behavior to one file +// +// hopefully a future clangd will not crash or lose track of its syntax highlighting, at which +// point the "bit_precise" namespace ought to be merged back into *bit-cast.cpp. + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define LITTLE_END 1 +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define LITTLE_END 0 +#else +# error "huh?" +#endif + +using uint8_t = unsigned char; + +template +constexpr To bit_cast(const From &from) { + static_assert(sizeof(To) == sizeof(From)); + return __builtin_bit_cast(To, from); +} + +namespace bit_precise { +// ok so it's a little bit of a lie to say we don't support _BitInt in any casts; we do in fact +// support casting _from_ a _BitInt(N), at least some of the time +static_assert(bit_cast(0xff) == 0xff); +template struct bytes { uint8_t b[N]; }; +static_assert(bit_cast, _BitInt(12)>(0xff).b[(LITTLE_END ? 0 : /* fixme */ 0)] == 0xff); +static_assert(bit_cast, _BitInt(24)>(0xff).b[(LITTLE_END ? 0 : /* fixme */ 2)] == 0xff); + +enum byte : unsigned char {}; // not std::byte + +constexpr _BitInt(7) z = 0x7f; +constexpr auto bad_cast = __builtin_bit_cast(byte, z); // expected-error {{constant expression}} +// expected-note@-1 {{'bit_precise::byte' is invalid}} +// expected-note@-2 {{byte [0]}} + +#if __clang_major__ > 17 +// This is #ifdef'd off to stop clangd from crashing every time I open this file in my editor +// fixme? this crashes clang17 and before +constexpr auto unsupported_cast = __builtin_bit_cast(uint8_t, z); // expected-error {{constant expression}} +// expected-note@-1 {{subobject of type 'const uint8_t' (aka 'const unsigned char') is not initialized}} +#endif + +// expected-note@+1 {{constexpr bit cast involving type '_BitInt(8)' is not yet supported}} +constexpr auto _n = __builtin_bit_cast(_BitInt(8), (uint8_t)0xff); // expected-error {{constant expression}} + +// expected-note@+1 {{constexpr bit cast involving type '_BitInt(7)' is not yet supported}} +constexpr auto _m = __builtin_bit_cast(_BitInt(7), (uint8_t)0xff); // expected-error {{constant expression}} + +// fixme: support _BitInt +// struct bitints { +// _BitInt(2) x; +// signed _BitInt(4) y; +// }; +// +// constexpr auto bi = bit_cast(0xff'ff); +// static_assert(bi.x == 0x3); +// static_assert(bi.y == -8); + +// fixme?: the syntax highlighting here is a little off (`signed` and `constexpr` both lose their "keyword" coloring) +struct BF { + _BitInt(2) x : 2; + signed _BitInt(3) y : 2; + // expected-warning@+1 {{exceeds the width of its type}} + _BitInt(3) z : 4; // "oversized" bit field +}; + +// expected-note@+1 {{constexpr bit cast involving type '_BitInt(2)' is not yet supported}} +constexpr auto bf = __builtin_bit_cast(BF, (uint8_t)0xff); // expected-error {{must be initialized by a constant expression}} + +// fixme: support _BitInt +// constexpr auto bf = bit_cast(0xff); +// static_assert(bf.x == 0x3); +// static_assert(bf.y == -4); // or +4 ? +// static_assert(bf.z == 0x7); + +} // namespace bit_precise diff --git a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp index c5b8032f40b13..1baff5f56941a 100644 --- a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp +++ b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp @@ -10,41 +10,35 @@ # error "huh?" #endif -template struct is_same { - static constexpr bool value = false; -}; -template struct is_same { - static constexpr bool value = true; -}; - static_assert(sizeof(int) == 4); static_assert(sizeof(long long) == 8); +using uint8_t = unsigned char; +using uint16_t = unsigned __INT16_TYPE__; +using uint32_t = unsigned __INT32_TYPE__; +using uint64_t = unsigned __INT64_TYPE__; + 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 - // expected-note@+2 2 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'signed char' is invalid}} -#endif return __builtin_bit_cast(To, from); } template -constexpr bool round_trip(const Init &init) { +constexpr bool check_round_trip(const Init &init) { return bit_cast(bit_cast(init)) == init; } +template +constexpr Init round_trip(const Init &init) { + return bit_cast(bit_cast(init)); +} + void test_int() { - static_assert(round_trip((int)-1)); - static_assert(round_trip((int)0x12345678)); - static_assert(round_trip((int)0x87654321)); - static_assert(round_trip((int)0x0C05FEFE)); + static_assert(check_round_trip((int)-1)); + static_assert(check_round_trip((int)0x12345678)); + static_assert(check_round_trip((int)0x87654321)); + static_assert(check_round_trip((int)0x0C05FEFE)); } void test_array() { @@ -73,8 +67,8 @@ void test_record() { ? 0x0C05FEFE : 0xCAFEBABE)); - static_assert(round_trip(splice)); - static_assert(round_trip(splice)); + static_assert(round_trip(splice) == splice); + static_assert(round_trip(splice) == splice); struct base2 { }; @@ -98,7 +92,7 @@ void test_record() { constexpr bases b = {{1, 2}, {}, {3}, 4}; constexpr tuple4 t4 = bit_cast(b); static_assert(t4 == tuple4{1, 2, 3, 4}); - static_assert(round_trip(b)); + static_assert(round_trip(b) == b); } void test_partially_initialized() { @@ -115,33 +109,327 @@ void test_partially_initialized() { static_assert(sizeof(pad) == sizeof(no_pad)); + constexpr auto cast = [](const pad& from) constexpr { + // expected-note@+6 2 {{bit_cast source expression (type 'const pad') does not produce a constant value for byte [1] (of {7..0}) which are required by target type 'no_pad' (subobject 'signed char')}} + #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 + // expected-note@+2 2 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'signed char' is invalid}} + #endif + return __builtin_bit_cast(no_pad, from); + }; + constexpr pad pir{4, 4}; // expected-error@+2 {{constexpr variable 'piw' must be initialized by a constant expression}} - // expected-note@+1 {{in call to 'bit_cast(pir)'}} - constexpr int piw = bit_cast(pir).x; + // expected-note@+1 {{in call}} + constexpr int piw = cast(pir).x; // expected-error@+2 {{constexpr variable 'bad' must be initialized by a constant expression}} - // expected-note@+1 {{in call to 'bit_cast(pir)'}} - constexpr no_pad bad = bit_cast(pir); + // expected-note@+1 {{in call}} + constexpr no_pad bad = cast(pir); constexpr pad fine = bit_cast(no_pad{1, 2, 3, 4, 5}); static_assert(fine.x == 1 && fine.y == 5); } -void no_bitfields() { - // FIXME! +template +struct bits { + T : Pad; + T bits : N; + + constexpr bool operator==(const T& rhs) const { + return bits == rhs; + } +}; + +template +constexpr bool operator==(const struct bits& lhs, const struct bits& rhs) { + return lhs.bits == rhs.bits; +} + +void test_bitfields() { + { + struct Q { + // cf. CGBitFieldInfo + // on a little-endian machine the bits "[count from] the + // least-significant-bit." + // so, by leaving a bit unused, we truncate the value's MSB. + + // however, on a big-endian machine we "imagine the bits + // counting from the most-significant-bit", so we truncate + // the LSB here. + uint16_t q : 15; + }; + constexpr unsigned char bytes[2] = {0xf3, 0xef}; + constexpr Q q = bit_cast(bytes); + static_assert(q.q == (LITTLE_END ? 0x6ff3 : (0xf3ee >> 1))); + static_assert(bit_cast(bytes) == (LITTLE_END + ? 0xeff3 + : 0xf3ef), + "bit-field casting ought to match \"whole\"-field casting"); + + // similarly, "skip 1 bit of padding" followed by "read 9 bits" + // will truncate (shift out) either the LSB (little endian) or MSB (big endian) + static_assert((0xf3ee >> 1) == 0x79f7); + static_assert(0x01cf == (0xf3ef >> (16-9-1) & 0x1ff)); + static_assert(bit_cast>(q) == (LITTLE_END + ? (0xeff3 >> 1) & 0x1ff + : (0xf3ef >> (16-9-1)) & 0x1ff)); + + #if LITTLE_END == 0 + // expected-note@+5 {{bit [0]}} + #else + // expected-note@+3 {{bit [15]}} + #endif + // expected-error@+1 {{constant expression}} + constexpr auto _i = __builtin_bit_cast(bits<15, uint16_t, 1>, q); + // expected-note@-1 {{indeterminate}} + } + + static_assert(round_trip, uint8_t>(0x8c) == 0x8c); + static_assert(round_trip, uint32_t>(0x8c0f'fee5) == 0x8c0ffee5); + + #define MSG "endianness matters even with <=8-bit fields" + static_assert(bit_cast, uint16_t>(0xcafe) == (LITTLE_END + ? 0x95 + : 0x7f), MSG); + static_assert(bit_cast, uint16_t>(0xcafe) == (LITTLE_END + ? 0x2 + : 0xf), MSG); + static_assert(bit_cast, uint32_t>(0xa1cafe) == (LITTLE_END + ? 0x4 + : 0x5), MSG); + #undef MSG + struct S { - unsigned char x : 8; + // little endian: + // MSB .... .... LSB + // |y| |x| + // + // big endian + // MSB .... .... LSB + // |x| |y| + + unsigned char x : 4; + unsigned char y : 4; + + constexpr bool operator==(S const &other) const { + return x == other.x && y == other.y; + } }; - struct G { - unsigned char x : 8; + constexpr S s{0xa, 0xb}; + static_assert(bit_cast>(s) == (LITTLE_END ? 0xba : 0xab)); + static_assert(bit_cast>(s) == (LITTLE_END + ? 0xba & 0x7f + : (0xab & 0xfe) >> 1)); + + static_assert(round_trip>(s) == s); + + struct R { + unsigned int r : 31; + unsigned int : 0; + unsigned int : 32; + constexpr bool operator==(R const &other) const { + return r == other.r; + } }; + using T = bits<31, signed __INT64_TYPE__>; - constexpr S s{0}; - // expected-error@+2 {{constexpr variable 'g' must be initialized by a constant expression}} - // expected-note@+1 {{constexpr bit_cast involving bit-field is not yet supported}} - constexpr G g = __builtin_bit_cast(G, s); + constexpr R r{0x4ac0ffee}; + constexpr T t = bit_cast(r); + static_assert(t == ((0xFFFFFFFF8 << 28) | 0x4ac0ffee)); // sign extension + + static_assert(round_trip(r) == r); + static_assert(round_trip(t) == t); + + struct U { + // expected-warning@+1 {{exceeds the width of its type}} + uint32_t trunc : 33; + uint32_t u : 31; + constexpr bool operator==(U const &other) const { + return trunc == other.trunc && u == other.u; + } + }; + struct V { + uint64_t notrunc : 32; + uint64_t : 1; + uint64_t v : 31; + constexpr bool operator==(V const &other) const { + return notrunc == other.notrunc && v == other.v; + } + }; + + constexpr U u{static_cast(~0), 0x4ac0ffee}; + constexpr V v = bit_cast(u); + static_assert(v.v == 0x4ac0ffee); + + { + #define MSG "a constexpr ought to produce padding bits from padding bits" + static_assert(round_trip(u) == u, MSG); + static_assert(round_trip(v) == v, MSG); + + constexpr auto w = bit_cast>(u); + static_assert(w == (LITTLE_END + ? 0x4ac0ffee & 0xFFF + : (0x4ac0ffee & (0xFFF << (31 - 12))) >> (31-12) + ), MSG); + #undef MSG + } + + // nested structures + { + struct J { + struct { + uint16_t k : 12; + } K; + struct { + uint16_t l : 4; + } L; + }; + + static_assert(sizeof(J) == 4); + constexpr J j = bit_cast(0x8c0ffee5); + + static_assert(j.K.k == (LITTLE_END ? 0xee5 : 0x8c0)); + static_assert(j.L.l == 0xf /* yay symmetry */); + static_assert(bit_cast>(j) == 0xf); + struct N { + bits<12, uint16_t> k; + uint16_t : 16; + }; + static_assert(bit_cast(j).k == j.K.k); + + struct M { + bits<4, uint16_t, 0> m[2]; + constexpr bool operator==(const M& rhs) const { + return m[0] == rhs.m[0] && m[1] == rhs.m[1]; + }; + }; + #if LITTLE_END == 1 + constexpr uint16_t want[2] = {0x5, 0xf}; + #else + constexpr uint16_t want[2] = {0x8000, 0xf000}; + #endif + + static_assert(bit_cast(j) == bit_cast(want)); + } + + // enums + { + // ensure we're packed into the top 2 bits + constexpr int pad = LITTLE_END ? 6 : 0; + struct X + { + char : pad; + enum class direction: char { left, right, up, down } direction : 2; + }; + + constexpr X x = { X::direction::down }; + static_assert(bit_cast>(x) == -1); + static_assert(bit_cast>(x) == 3); + static_assert( + bit_cast((unsigned char)0x40).direction == X::direction::right); + } +} + +namespace std { +enum byte : unsigned char {}; +} // namespace std + +using uint8_t = unsigned char; + +template +struct bytes { + using size_t = unsigned int; + unsigned char d[N]; + + constexpr unsigned char &operator[](size_t index) { + if (index < N) + return d[index]; + } +}; + +void bitfield_indeterminate() { + struct BF { unsigned char z : 2; }; + enum byte : unsigned char {}; + + constexpr BF bf = {0x3}; + static_assert(bit_cast>(bf).bits == bf.z); + + // expected-error@+1 {{not an integral constant expression}} + static_assert(bit_cast(bf)); + /// FIXME the above doesn't get any helpful notes, but the below does +#if LITTLE_END == 1 + // expected-note@+6 {{bit [2-7]}} +#else + // expected-note@+4 {{bit [0-5]}} +#endif + // expected-note@+2 {{indeterminate}} + // expected-error@+1 {{not an integral constant expression}} + static_assert(__builtin_bit_cast(byte, bf)); + + struct M { + // expected-note@+1 {{subobject declared here}} + unsigned char mem[sizeof(BF)]; + }; + // expected-error@+2 {{initialized by a constant expression}} + // expected-note@+1 {{not initialized}} + constexpr M m = bit_cast(bf); + + constexpr auto f = []() constexpr { + // bits<24, unsigned int, LITTLE_END ? 0 : 8> B = {0xc0ffee}; + constexpr struct { unsigned short b1; unsigned char b0; } B = {0xc0ff, 0xee}; + return bit_cast>(B); + }; + + static_assert(f()[0] + f()[1] + f()[2] == 0xc0 + 0xff + 0xee); + { + // expected-error@+2 {{initialized by a constant expression}} + // expected-note@+1 {{read of uninitialized object is not allowed in a constant expression}} + constexpr auto _bad = f()[3]; + } + + struct B { + unsigned short s0 : 8; + unsigned short s1 : 8; + std::byte b0 : 4; + std::byte b1 : 4; + std::byte b2 : 4; + }; + constexpr auto g = [f]() constexpr { + return bit_cast(f()); + }; + static_assert(g().s0 + g().s1 + g().b0 + g().b1 == 0xc0 + 0xff + 0xe + 0xe); + { + // expected-error@+2 {{initialized by a constant expression}} + // expected-note@+1 {{read of uninitialized object is not allowed in a constant expression}} + constexpr auto _bad = g().b2; + } +} + +void bitfield_unsupported() { + // if a future standard requires more types to be permitted in the + // declaration of a bit-field, then this test will hopefully indicate + // that there's work to be done on __builtin_bit_cast. + struct U { + // expected-error@+1 {{bit-field 'f' has non-integral type}} + bool f[8] : 8; + }; + + // this next bit is speculative: if the above _were_ a valid definition, + // then the below might also be a reasonable interpretation of its + // semantics, but the current implementation of __builtin_bit_cast will + // fail + + // expected-note@+3 {{invalid declaration}} FIXME should we instead bail out in Sema? + // expected-note@+2 {{declared here}} + // expected-error@+1 {{initialized by a constant expression}} + constexpr U u = __builtin_bit_cast(U, (char)0b1010'0101); + static_assert(U.f[0] && U.f[2] && U.f[4] && U.f[8]); + // expected-note@+2 {{not a constant expression}} + // expected-error@+1 {{not an integral constant expression}} + static_assert(__builtin_bit_cast(bits<8>, u) == 0xA5); } void array_members() { @@ -165,8 +453,8 @@ void array_members() { constexpr G g = bit_cast(s); static_assert(g.a == 1 && g.b == 2 && g.c == 3); - static_assert(round_trip(s)); - static_assert(round_trip(g)); + static_assert(check_round_trip(s)); + static_assert(check_round_trip(g)); } void bad_types() { @@ -229,6 +517,7 @@ void test_array_fill() { typedef decltype(nullptr) nullptr_t; +// expected-note@+7 {{byte [0-7]}} #ifdef __CHAR_UNSIGNED__ // expected-note@+5 {{indeterminate value can only initialize an object of type 'unsigned char', 'char', or 'std::byte'; 'unsigned long' is invalid}} #else @@ -350,10 +639,6 @@ constexpr A two() { } constexpr short good_two = two().c + two().s; -namespace std { -enum byte : unsigned char {}; -} - enum my_byte : unsigned char {}; struct pad { @@ -364,16 +649,18 @@ struct pad { constexpr int ok_byte = (__builtin_bit_cast(std::byte[8], pad{1, 2}), 0); constexpr int ok_uchar = (__builtin_bit_cast(unsigned char[8], pad{1, 2}), 0); +// expected-note@+7 {{bit_cast source expression (type 'pad') does not produce a constant value for byte [1] (of {7..0}) which are required by target type 'my_byte[8]' (subobject 'my_byte')}} #ifdef __CHAR_UNSIGNED__ -// expected-note@+5 {{indeterminate value can only initialize an object of type 'unsigned char', 'char', or 'std::byte'; 'my_byte' is invalid}}}} +// expected-note@+5 {{indeterminate value can only initialize an object of type 'unsigned char', 'char', or 'std::byte'; 'my_byte' is invalid}} #else // expected-note@+3 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'my_byte' is invalid}} #endif // expected-error@+1 {{constexpr variable 'bad_my_byte' must be initialized by a constant expression}} constexpr int bad_my_byte = (__builtin_bit_cast(my_byte[8], pad{1, 2}), 0); #ifndef __CHAR_UNSIGNED__ -// expected-error@+3 {{constexpr variable 'bad_char' must be initialized by a constant expression}} -// expected-note@+2 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'char' is invalid}} +// expected-note@+4 {{bit_cast source expression (type 'pad') does not produce a constant value for byte [1] (of {7..0}) which are required by target type 'char[8]' (subobject 'char')}} +// expected-note@+3 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'char' is invalid}} +// expected-error@+2 {{constexpr variable 'bad_char' must be initialized by a constant expression}} #endif constexpr int bad_char = (__builtin_bit_cast(char[8], pad{1, 2}), 0); @@ -404,29 +691,126 @@ constexpr unsigned char identity3b = __builtin_bit_cast(unsigned char, identity3 namespace test_bool { -constexpr bool test_bad_bool = bit_cast('A'); // expected-error {{must be initialized by a constant expression}} expected-note{{in call}} +// expected-note@+1 {{cannot be represented in type 'bool'}} +constexpr bool test_bad_bool = __builtin_bit_cast(bool, 'A'); // expected-error {{must be initialized by a constant expression}} + +static_assert(round_trip(true)); +static_assert(round_trip(true)); +static_assert(round_trip(false) == false); -static_assert(round_trip(true), ""); -static_assert(round_trip(false), ""); -static_assert(round_trip(false), ""); +static_assert(static_cast(false) == 0x0); +static_assert(bit_cast(false) == 0x0); +static_assert(static_cast(true) == 0x1); +static_assert(bit_cast(true) == 0x1); -static_assert(round_trip((char)0), ""); -static_assert(round_trip((char)1), ""); +static_assert(round_trip(0x01) == 0x1); +static_assert(round_trip(0x00) == 0x0); +// expected-note@+2 {{cannot be represented in type 'bool'}} +// expected-error@+1 {{constant expression}} +constexpr auto test_bad_bool2 = __builtin_bit_cast(bool, (uint8_t)0x02); + +#if LITTLE_END == 1 +constexpr auto okbits = bit_cast>(true); +#else +constexpr auto okbits = bit_cast>(true); +#endif +static_assert(okbits == 0x1); +// expected-note@+3 {{bit [1-7]}} +// expected-note@+2 {{or 'std::byte'; 'bool' is invalid}} +// expected-error@+1 {{constant expression}} +constexpr auto _weird_bool = __builtin_bit_cast(bool, okbits); + +// these don't work because we're trying to read the whole 8 bits to ensure +// the value is representable, as above +// static_assert(round_trip>({0x1}) == 0x1); +// static_assert(round_trip>({0x0}) == 0x0); + +// these work because we're only reading 1 bit of "bool" to ensure +// "representability" +static_assert(round_trip, bits<1>>({0x1}) == 0x1); +static_assert(round_trip, bits<1>>({0x0}) == 0x0); + +template +constexpr bool extract_bit(unsigned char v) { + return static_cast(bit_cast>(v).bits); } +// 0xA5 is a palindrome, so endianness doesn't matter +// (counting LSB->MSB is the same as MSB->LSB) +static_assert(extract_bit<0>(0xA5) == 0x1); +static_assert(extract_bit<2>(0xA5) == 0x1); +static_assert(extract_bit<5>(0xA5) == 0x1); +static_assert(extract_bit<7>(0xA5) == 0x1); + +static_assert(extract_bit<1>(0xA5) == 0x0); +static_assert(extract_bit<3>(0xA5) == 0x0); +static_assert(extract_bit<4>(0xA5) == 0x0); +static_assert(extract_bit<6>(0xA5) == 0x0); + +enum byte : unsigned char {}; // not std::byte or unsigned char + +static_assert(extract_bit<5, byte>('\xa5') == 0x1); + +struct pad { +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + bool : 5; // push field down to the LSB +#endif + bool b : 3; +}; + +static_assert(bit_cast(0b001).b == true); +static_assert(bit_cast(0b000).b == false); + +// expected-note@+1 {{cannot be represented in type 'bool'}} +constexpr auto _bad_bool3 = __builtin_bit_cast(pad, (uint8_t)0b110); // expected-error {{must be initialized by a constant expression}} + +struct S { +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + byte : 7; +#endif + byte z : 1; +}; + +constexpr auto s = bit_cast(pad{1}); +static_assert(s.z == 0x1); + +// expected-note@+3 {{bit [1-2]}} +// expected-note@+2 {{or 'std::byte'; 'bool' is invalid}} +// expected-error@+1 {{constant expression}} +constexpr auto _bad_bool4 = __builtin_bit_cast(pad, s); + + +// `bool` includes padding bits, but *which* single bit stores the +// value is under-specified. These tests not-so-secretly assert that +// it's in fact the LSB that the compiler "sees" as the value. +struct pack { + bool a : 1; + bool b : 1; + + // 1 bit of value, 5 bits of padding + bool c : 6; +}; + +constexpr auto packed = bit_cast(LITTLE_END ? 0x07 : 0xc1); +static_assert(packed.a && packed.b && packed.c); + +static_assert(bit_cast>(packed) == 0x3); +static_assert(bit_cast>(packed) == 0x1); + +} // namespace test_bool 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}} +// expected-note@+2 {{byte [10-15]}} +// expected-note@+1 {{or 'std::byte'; '__int128' is invalid}} +constexpr __int128_t test_cast_to_int128 = __builtin_bit_cast(__int128_t, (long double)0); // expected-error{{must be initialized by a constant expression}} constexpr long double ld = 3.1425926539; -struct bytes { - unsigned char d[16]; -}; +using bytes = bytes<16>; -static_assert(round_trip(ld), ""); +static_assert(check_round_trip(ld)); -static_assert(round_trip(10.0L)); +static_assert(check_round_trip(10.0L)); constexpr bool f(bool read_uninit) { bytes b = bit_cast(ld); @@ -436,17 +820,17 @@ constexpr bool f(bool read_uninit) { }; for (int i = 0; i != 10; ++i) - if (ld_bytes[i] != b.d[i]) + if (ld_bytes[i] != b[i]) return false; - if (read_uninit && b.d[10]) // expected-note{{read of uninitialized object is not allowed in a constant expression}} + if (read_uninit && b[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 assertion expression is not an integral constant expression}} expected-note{{in call to 'f(true)'}} +static_assert(f(/*read_uninit=*/false)); +static_assert(f(/*read_uninit=*/true)); // expected-error{{static assertion expression is not an integral constant expression}} expected-note{{in call to 'f(true)'}} constexpr bytes ld539 = { 0x0, 0x0, 0x0, 0x0, @@ -457,12 +841,12 @@ constexpr bytes ld539 = { constexpr long double fivehundredandthirtynine = 539.0; -static_assert(bit_cast(ld539) == fivehundredandthirtynine, ""); +static_assert(bit_cast(ld539) == fivehundredandthirtynine); #else static_assert(round_trip<__int128_t>(34.0L)); #endif -} +} // namespace test_long_double namespace test_vector { @@ -473,10 +857,10 @@ constexpr uint2 test_vector = { 0x0C05FEFE, 0xCAFEBABE }; static_assert(bit_cast(test_vector) == (LITTLE_END ? 0xCAFEBABE0C05FEFE - : 0x0C05FEFECAFEBABE), ""); + : 0x0C05FEFECAFEBABE)); -static_assert(round_trip(0xCAFEBABE0C05FEFEULL), ""); -static_assert(round_trip(0xCAFEBABE0C05FEFEULL), ""); +static_assert(check_round_trip(0xCAFEBABE0C05FEFEULL)); +static_assert(check_round_trip(0xCAFEBABE0C05FEFEULL)); typedef bool bool8 __attribute__((ext_vector_type(8))); typedef bool bool9 __attribute__((ext_vector_type(9))); @@ -485,16 +869,16 @@ typedef bool bool17 __attribute__((ext_vector_type(17))); typedef bool bool32 __attribute__((ext_vector_type(32))); typedef bool bool128 __attribute__((ext_vector_type(128))); -static_assert(bit_cast(bool8{1,0,1,0,1,0,1,0}) == (LITTLE_END ? 0x55 : 0xAA), ""); -static_assert(round_trip(static_cast(0)), ""); -static_assert(round_trip(static_cast(1)), ""); -static_assert(round_trip(static_cast(0x55)), ""); +static_assert(bit_cast(bool8{1,0,1,0,1,0,1,0}) == (LITTLE_END ? 0x55 : 0xAA)); +static_assert(round_trip('\x00') == 0); +static_assert(round_trip('\x01') == 0x1); +static_assert(round_trip('\x55') == 0x55); -static_assert(bit_cast(bool16{1,1,1,1,1,0,0,0, 1,1,1,1,0,1,0,0}) == (LITTLE_END ? 0x2F1F : 0xF8F4), ""); +static_assert(bit_cast(bool16{1,1,1,1,1,0,0,0, 1,1,1,1,0,1,0,0}) == (LITTLE_END ? 0x2F1F : 0xF8F4)); -static_assert(round_trip(static_cast(0xCAFE)), ""); -static_assert(round_trip(static_cast(0xCAFEBABE)), ""); -static_assert(round_trip(static_cast<__int128_t>(0xCAFEBABE0C05FEFEULL)), ""); +static_assert(check_round_trip(static_cast(0xCAFE))); +static_assert(check_round_trip(static_cast(0xCAFEBABE))); +static_assert(check_round_trip(static_cast<__int128_t>(0xCAFEBABE0C05FEFEULL))); // expected-error@+2 {{constexpr variable 'bad_bool9_to_short' must be initialized by a constant expression}} // expected-note@+1 {{bit_cast involving type 'bool __attribute__((ext_vector_type(9)))' (vector of 9 'bool' values) is not allowed in a constant expression; element size 1 * element count 9 is not a multiple of the byte size 8}} @@ -506,4 +890,21 @@ constexpr bool9 bad_short_to_bool9 = __builtin_bit_cast(bool9, static_cast(bit_cast(0xa1c0ffee)); +static_assert(p.s == (LITTLE_END ? 0xffee : 0xa1c0)); +static_assert(p.c == (LITTLE_END ? 0xc0 : 0xff)); + +#if LITTLE_END == 1 +// expected-note@+5 {{for byte [3]}} +#else +// expected-note@+3 {{for byte [0]}} +#endif +// expected-note@+1 {{indeterminate value}} +constexpr auto _bad_p = __builtin_bit_cast(bool32, p); // expected-error {{initialized by a constant expression}} + + +} // namespace test_vector diff --git a/clang/test/SemaCXX/static-assert-cxx17.cpp b/clang/test/SemaCXX/static-assert-cxx17.cpp index 41a7b025d0eb7..1d78915aa13e1 100644 --- a/clang/test/SemaCXX/static-assert-cxx17.cpp +++ b/clang/test/SemaCXX/static-assert-cxx17.cpp @@ -94,7 +94,7 @@ void foo6() { // expected-error@-1{{static assertion failed due to requirement '(const X *)nullptr'}} static_assert(static_cast *>(nullptr)); // expected-error@-1{{static assertion failed due to requirement 'static_cast *>(nullptr)'}} - static_assert((const X[]){} == nullptr); + static_assert((const X[]){} == nullptr); // expected-note{{expression evaluates to '{} == nullptr'}} // expected-error@-1{{static assertion failed due to requirement '(const X[0]){} == nullptr'}} static_assert(sizeof(X().X::~X())>) == 0); // expected-error@-1{{static assertion failed due to requirement 'sizeof(X) == 0'}} \ diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp index 792dc78464b2a..f6fbea4ab03d7 100644 --- a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp +++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp @@ -216,15 +216,19 @@ namespace UnnamedBitfield { // uninitialized and it being zeroed. Those are not distinct states // according to [temp.type]p2. // - // FIXME: We shouldn't track a value for unnamed bit-fields, nor number - // them when computing field indexes. + // At namespace scope, multiple `using` declarations are valid (to avoid + // conflicts when #including), just in case they they "all refer to the same + // entity." This test makes use of that implicit constraint to ensure that + // the compiler does not "see" a difference between any of the `T`s below. + // cf. https://stackoverflow.com/a/31225016/151464 template struct X {}; constexpr A a; using T = X; using T = X; using T = X<(A())>; - // Once we support bit-casts involving bit-fields, this should be valid too. - using T = X<__builtin_bit_cast(A, 0)>; // expected-error {{constant}} expected-note {{not yet supported}} + using T = X<__builtin_bit_cast(A, 0)>; + using T = X<__builtin_bit_cast(A, A{})>; + using T = X<__builtin_bit_cast(A, (unsigned char[4]){})>; } namespace Temporary {