Index: clang/lib/AST/CMakeLists.txt =================================================================== --- clang/lib/AST/CMakeLists.txt +++ clang/lib/AST/CMakeLists.txt @@ -74,6 +74,7 @@ Interp/Frame.cpp Interp/Function.cpp Interp/InterpBuiltin.cpp + Interp/InterpBitcast.cpp Interp/Floating.cpp Interp/Interp.cpp Interp/InterpBlock.cpp Index: clang/lib/AST/Interp/Boolean.h =================================================================== --- clang/lib/AST/Interp/Boolean.h +++ clang/lib/AST/Interp/Boolean.h @@ -9,14 +9,15 @@ #ifndef LLVM_CLANG_AST_INTERP_BOOLEAN_H #define LLVM_CLANG_AST_INTERP_BOOLEAN_H -#include -#include #include "Integral.h" #include "clang/AST/APValue.h" +#include "clang/AST/ASTContext.h" #include "clang/AST/ComparisonCategories.h" #include "llvm/ADT/APSInt.h" #include "llvm/Support/MathExtras.h" #include "llvm/Support/raw_ostream.h" +#include +#include namespace clang { namespace interp { @@ -65,6 +66,9 @@ Boolean toUnsigned() const { return *this; } constexpr static unsigned bitWidth() { return 1; } + constexpr static unsigned objectReprBits() { return 8; } + constexpr static unsigned valueReprBytes(const ASTContext &Ctx) { return 1; } + bool isZero() const { return !V; } bool isMin() const { return isZero(); } @@ -105,6 +109,13 @@ return Boolean(!Value.isZero()); } + static Boolean bitcastFromMemory(const std::byte *Buff) { + bool Val = static_cast(*Buff); + return Boolean(Val); + } + + void bitcastToMemory(std::byte *Buff) { std::memcpy(Buff, &V, sizeof(V)); } + static Boolean zero() { return from(false); } template Index: clang/lib/AST/Interp/ByteCodeExprGen.h =================================================================== --- clang/lib/AST/Interp/ByteCodeExprGen.h +++ clang/lib/AST/Interp/ByteCodeExprGen.h @@ -265,6 +265,7 @@ const RecordType *BaseType, const Expr *E); unsigned collectBaseOffset(const RecordType *BaseType, const RecordType *DerivedType); + bool emitBuiltinBitCast(const CastExpr *E); protected: /// Variable to storage mapping. Index: clang/lib/AST/Interp/ByteCodeExprGen.cpp =================================================================== --- clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -60,6 +60,74 @@ } // namespace interp } // namespace clang +// This function is constexpr if and only if To, From, and the types of +// all subobjects of To and From are types T such that... +// (3.1) - is_union_v is false; +// (3.2) - is_pointer_v is false; +// (3.3) - is_member_pointer_v is false; +// (3.4) - is_volatile_v is false; and +// (3.5) - T has no non-static data members of reference type +template +bool ByteCodeExprGen::emitBuiltinBitCast(const CastExpr *E) { + const Expr *SubExpr = E->getSubExpr(); + QualType FromType = SubExpr->getType(); + QualType ToType = E->getType(); + std::optional ToT = classify(ToType); + + // FIXME: This is wrong. We need to do the bitcast and then + // throw away the result, so we still get the diagnostics. + if (DiscardResult) + return this->discard(SubExpr); + + if (ToType->isNullPtrType()) { + if (!this->discard(SubExpr)) + return false; + + return this->emitNullPtr(E); + } + + if (FromType->isNullPtrType() && ToT) { + if (!this->discard(SubExpr)) + return false; + + return visitZeroInitializer(ToType, E); + } + assert(!ToType->isReferenceType()); + + // Get a pointer to the value-to-cast on the stack. + if (!this->visit(SubExpr)) + return false; + + if (!ToT || ToT == PT_Ptr) { + // Conversion to an array or record type. + return this->emitBitCastPtr(E); + } + + assert(ToT); + + // Conversion to a primitive type. FromType can be another + // primitive type, or a record/array. + // + // Same thing for floats, but we need the target + // semantics here. + if (ToT == PT_Float) { + const auto *TargetSemantics = &Ctx.getFloatSemantics(ToType); + CharUnits FloatSize = Ctx.getASTContext().getTypeSizeInChars(ToType); + return this->emitBitCastFP(TargetSemantics, FloatSize.getQuantity(), E); + } + + bool ToTypeIsUChar = (ToType->isSpecificBuiltinType(BuiltinType::UChar) || + ToType->isSpecificBuiltinType(BuiltinType::Char_U)); + + if (!this->emitBitCast(*ToT, ToTypeIsUChar || ToType->isStdByteType(), E)) + return false; + + if (DiscardResult) + return this->emitPop(*ToT, E); + + return true; +} + template bool ByteCodeExprGen::VisitCastExpr(const CastExpr *CE) { auto *SubExpr = CE->getSubExpr(); @@ -80,6 +148,9 @@ }); } + case CK_LValueToRValueBitCast: + return this->emitBuiltinBitCast(CE); + case CK_UncheckedDerivedToBase: case CK_DerivedToBase: { if (!this->visit(SubExpr)) @@ -155,6 +226,7 @@ case CK_NonAtomicToAtomic: case CK_NoOp: case CK_UserDefinedConversion: + case CK_BitCast: if (DiscardResult) return this->discard(SubExpr); return this->visit(SubExpr); @@ -1644,6 +1716,8 @@ return this->visit(CE); } else if (const auto *DIE = dyn_cast(Initializer)) { return this->visitInitializer(DIE->getExpr()); + } else if (const auto *BBCE = dyn_cast(Initializer)) { + return this->visit(BBCE); } else if (const auto *CE = dyn_cast(Initializer)) { return this->visitInitializer(CE->getSubExpr()); } else if (const auto *CE = dyn_cast(Initializer)) { Index: clang/lib/AST/Interp/Floating.h =================================================================== --- clang/lib/AST/Interp/Floating.h +++ clang/lib/AST/Interp/Floating.h @@ -15,6 +15,7 @@ #include "Primitives.h" #include "clang/AST/APValue.h" +#include "clang/AST/ASTContext.h" #include "llvm/ADT/APFloat.h" namespace clang { @@ -78,6 +79,12 @@ } unsigned bitWidth() const { return F.semanticsSizeInBits(F.getSemantics()); } + unsigned objectReprBits() { return F.semanticsSizeInBits(F.getSemantics()); } + + unsigned valueReprBytes(const ASTContext &Ctx) { + return Ctx.toCharUnitsFromBits(F.semanticsSizeInBits(F.getSemantics())) + .getQuantity(); + } bool isSigned() const { return true; } bool isNegative() const { return F.isNegative(); } @@ -103,6 +110,20 @@ return Status; } + static Floating bitcastFromMemory(const std::byte *Buff, + const llvm::fltSemantics &Sem) { + size_t Size = APFloat::semanticsSizeInBits(Sem); + llvm::APInt API(Size, true); + llvm::LoadIntFromMemory(API, (const uint8_t *)Buff, Size / 8); + + return Floating(APFloat(Sem, API)); + } + + void bitcastToMemory(std::byte *Buff) { + llvm::APInt API = F.bitcastToAPInt(); + llvm::StoreIntToMemory(API, (uint8_t *)Buff, bitWidth() / 8); + } + // ------- static APFloat::opStatus add(const Floating &A, const Floating &B, Index: clang/lib/AST/Interp/Integral.h =================================================================== --- clang/lib/AST/Interp/Integral.h +++ clang/lib/AST/Interp/Integral.h @@ -13,8 +13,9 @@ #ifndef LLVM_CLANG_AST_INTERP_INTEGRAL_H #define LLVM_CLANG_AST_INTERP_INTEGRAL_H -#include "clang/AST/ComparisonCategories.h" #include "clang/AST/APValue.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ComparisonCategories.h" #include "llvm/ADT/APSInt.h" #include "llvm/Support/MathExtras.h" #include "llvm/Support/raw_ostream.h" @@ -111,6 +112,10 @@ } constexpr static unsigned bitWidth() { return Bits; } + constexpr static unsigned objectReprBits() { return Bits; } + constexpr static unsigned valueReprBytes(const ASTContext &Ctx) { + return Ctx.toCharUnitsFromBits(Bits).getQuantity(); + } bool isZero() const { return !V; } @@ -177,6 +182,18 @@ return Integral(Value); } + static Integral bitcastFromMemory(const std::byte *Buff) { + ReprT V; + + std::memcpy(&V, Buff, sizeof(ReprT)); + return Integral(V); + } + + void bitcastToMemory(std::byte *Buff) const { + assert(Buff); + std::memcpy(Buff, &V, sizeof(ReprT)); + } + static bool inRange(int64_t Value, unsigned NumBits) { return CheckRange(Value); } Index: clang/lib/AST/Interp/Interp.h =================================================================== --- clang/lib/AST/Interp/Interp.h +++ clang/lib/AST/Interp/Interp.h @@ -173,12 +173,27 @@ /// in the current context. bool CheckFloatResult(InterpState &S, CodePtr OpPC, APFloat::opStatus Status); +bool CheckBitcast(InterpState &S, CodePtr OpPC, unsigned IndeterminateBits, + bool TargetIsUCharOrByte); + /// Interpreter entry point. bool Interpret(InterpState &S, APValue &Result); /// Interpret a builtin function. bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F); +/// Perform a bitcast of all fields of P into Buff. This performs the +/// actions of a __builtin_bit_cast expression when the target type +/// is primitive. +bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff, + size_t BuffSize, unsigned &IndeterminateBits); + +/// Perform a bitcast of all fields of P into the fields of DestPtr. +/// This performs the actions of a __builtin_bit_cast expression when +/// the target type is a composite type. +bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, + CodePtr PC); + enum class ArithOp { Add, Sub }; //===----------------------------------------------------------------------===// @@ -1486,6 +1501,57 @@ return true; } +template ::T> +bool BitCast(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte) { + const Pointer &FromPtr = S.Stk.pop(); + + size_t BuffSize = ToT::valueReprBytes(S.getCtx()); + std::vector Buff(BuffSize); + unsigned IndeterminateBits = 0; + + if (!DoBitCast(S, OpPC, FromPtr, Buff.data(), BuffSize, IndeterminateBits)) + return false; + + if (!CheckBitcast(S, OpPC, IndeterminateBits, TargetIsUCharOrByte)) + return false; + + S.Stk.push(ToT::bitcastFromMemory(Buff.data())); + return true; +} + +/// Bitcast TO a float. +inline bool BitCastFP(InterpState &S, CodePtr OpPC, + const llvm::fltSemantics *Sem, uint32_t TargetSize) { + const Pointer &FromPtr = S.Stk.pop(); + + std::vector Buff(TargetSize); + unsigned IndeterminateBits = 0; + + if (!DoBitCast(S, OpPC, FromPtr, Buff.data(), TargetSize, IndeterminateBits)) + return false; + + if (!CheckBitcast(S, OpPC, IndeterminateBits, /*TargetIsUCharOrByte=*/false)) + return false; + + S.Stk.push(Floating::bitcastFromMemory(Buff.data(), *Sem)); + return true; +} + +/// 1) Pops a pointer from the stack +/// 2) Peeks a pointer +/// 3) Bitcasts the contents of the first pointer to the +/// fields of the second pointer. +inline bool BitCastPtr(InterpState &S, CodePtr OpPC) { + const Pointer &FromPtr = S.Stk.pop(); + Pointer &ToPtr = S.Stk.peek(); + + // FIXME: We should CheckLoad() for FromPtr and ToPtr here, I think. + if (!DoBitCastToPtr(S, FromPtr, ToPtr, OpPC)) + return false; + + return true; +} + /// 1) Pops a Floating from the stack. /// 2) Pushes a new floating on the stack that uses the given semantics. inline bool CastFP(InterpState &S, CodePtr OpPC, const llvm::fltSemantics *Sem, Index: clang/lib/AST/Interp/Interp.cpp =================================================================== --- clang/lib/AST/Interp/Interp.cpp +++ clang/lib/AST/Interp/Interp.cpp @@ -530,6 +530,24 @@ return true; } +bool CheckBitcast(InterpState &S, CodePtr OpPC, unsigned IndeterminateBits, + bool TargetIsUCharOrByte) { + + // This is always fine. + if (IndeterminateBits == 0) + return true; + + // Indeterminate bits can only be bitcast to unsigned char or std::byte. + if (TargetIsUCharOrByte) + return true; + + const Expr *E = S.Current->getExpr(OpPC); + QualType ExprType = E->getType(); + S.FFDiag(E, diag::note_constexpr_bit_cast_indet_dest) + << ExprType << S.getLangOpts().CharIsSigned << E->getSourceRange(); + return false; +} + bool Interpret(InterpState &S, APValue &Result) { // The current stack frame when we started Interpret(). // This is being used by the ops to determine wheter Index: clang/lib/AST/Interp/InterpBitcast.cpp =================================================================== --- /dev/null +++ clang/lib/AST/Interp/InterpBitcast.cpp @@ -0,0 +1,482 @@ +//===--- InterpBitcast.cpp - Interpreter for the constexpr VM ---*- 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 +// +//===----------------------------------------------------------------------===// +#include "Boolean.h" +#include "Interp.h" +#include "PrimType.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecordLayout.h" +#include "clang/Basic/Builtins.h" +#include "clang/Basic/TargetInfo.h" + +namespace clang { +namespace interp { + +// TODO: Try to e-duplicate the primitive and composite versions. + +/// Used to iterate over pointer fields. +using DataFunc = + llvm::function_ref; + +#define BITCAST_TYPE_SWITCH(Expr, B) \ + do { \ + switch (Expr) { \ + TYPE_SWITCH_CASE(PT_Sint8, B) \ + TYPE_SWITCH_CASE(PT_Uint8, B) \ + TYPE_SWITCH_CASE(PT_Sint16, B) \ + TYPE_SWITCH_CASE(PT_Uint16, B) \ + TYPE_SWITCH_CASE(PT_Sint32, B) \ + TYPE_SWITCH_CASE(PT_Uint32, B) \ + TYPE_SWITCH_CASE(PT_Sint64, B) \ + TYPE_SWITCH_CASE(PT_Uint64, B) \ + TYPE_SWITCH_CASE(PT_Bool, B) \ + default: \ + llvm_unreachable("Unhandled bitcast type"); \ + } \ + } while (0) + +/// Float is a special case that sometimes needs the floating point semantics +/// to be available. +#define BITCAST_TYPE_SWITCH_WITH_FLOAT(Expr, B) \ + do { \ + switch (Expr) { \ + TYPE_SWITCH_CASE(PT_Sint8, B) \ + TYPE_SWITCH_CASE(PT_Uint8, B) \ + TYPE_SWITCH_CASE(PT_Sint16, B) \ + TYPE_SWITCH_CASE(PT_Uint16, B) \ + TYPE_SWITCH_CASE(PT_Sint32, B) \ + TYPE_SWITCH_CASE(PT_Uint32, B) \ + TYPE_SWITCH_CASE(PT_Sint64, B) \ + TYPE_SWITCH_CASE(PT_Uint64, B) \ + TYPE_SWITCH_CASE(PT_Bool, B) \ + TYPE_SWITCH_CASE(PT_Float, B) \ + default: \ + llvm_unreachable("Unhandled bitcast type"); \ + } \ + } while (0) + +/// Rotate things around for big endian targets. +static void swapBytes(std::byte *M, size_t N) { + for (size_t I = 0; I != (N / 2); ++I) + std::swap(M[I], M[N - 1 - I]); +} + +/// Track what bytes have been initialized to known values and which ones +/// have indeterminate value. +/// All offsets are in bytes. +struct ByteTracker { + std::vector Initialized; + std::vector Data; + + ByteTracker() = default; + + size_t size() const { + assert(Initialized.size() == Data.size()); + return Initialized.size(); + } + + std::byte *getBytes(size_t Offset) { return Data.data() + Offset; } + bool allInitialized(size_t Offset, size_t Size) const { + for (size_t I = Offset; I != (Size + Offset); ++I) { + if (!Initialized[I]) + return false; + } + return true; + } + + std::byte *getWritableBytes(size_t Offset, size_t Size, bool InitValue) { + assert(Offset >= Data.size()); + assert(Size > 0); + + size_t OldSize = Data.size(); + Data.resize(Offset + Size); + + // Everything from the old size to the new offset is indeterminate. + for (size_t I = OldSize; I != Offset; ++I) + Initialized.push_back(false); + for (size_t I = Offset; I != Offset + Size; ++I) + Initialized.push_back(InitValue); + + return Data.data() + Offset; + } + + void markUninitializedUntil(size_t Offset) { + assert(Offset >= Data.size()); + + size_t NBytes = Offset - Data.size(); + for (size_t I = 0; I != NBytes; ++I) + Initialized.push_back(false); + Data.resize(Offset); + } + + void zeroUntil(size_t Offset) { + assert(Offset >= Data.size()); + + assert(Data.size() == Initialized.size()); + size_t NBytes = Offset - Data.size(); + for (size_t I = 0; I != NBytes; ++I) { + Initialized.push_back(true); + Data.push_back(std::byte{0}); + } + } +}; + +struct BitcastBuffer { + std::byte *Buff; + size_t ByteOffset = 0; + size_t Offset = 0; + size_t BuffSize; + unsigned IndeterminateBits = 0; + bool BigEndian; + + constexpr BitcastBuffer(std::byte *Buff, size_t BuffSize, bool BigEndian) + : Buff(Buff), BuffSize(BuffSize), BigEndian(BigEndian) {} + + std::byte *getBytes(size_t ByteOffset, size_t N) { + assert(ByteOffset >= this->ByteOffset && "we don't support stepping back"); + + // All untouched bits before the requested bit offset + // are indeterminate values. This will be important later, + // because they can't be read into non-uchar/non-std::byte + // values. + IndeterminateBits += (ByteOffset - this->ByteOffset); + + size_t OldOffset = this->Offset; + + this->Offset += N; + this->ByteOffset = ByteOffset + N; + + if (BigEndian) + return Buff + BuffSize - OldOffset - N; + + // Little Endian target. + return Buff + OldOffset; + } +}; + +/// We use this to recursively iterate over all fields and elemends of a pointer +/// and extract relevant data for a bitcast. +static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset, + DataFunc F) { + const Descriptor *FieldDesc = P.getFieldDesc(); + assert(FieldDesc); + + // Primitives. + if (FieldDesc->isPrimitive()) + return F(P, *Ctx.classify(FieldDesc->getType()), Offset); + + // Primitive arrays. + if (FieldDesc->isPrimitiveArray()) { + QualType ElemType = + FieldDesc->getType()->getAsArrayTypeUnsafe()->getElementType(); + size_t ElemSize = + Ctx.getASTContext().getTypeSizeInChars(ElemType).getQuantity(); + PrimType ElemT = *Ctx.classify(ElemType); + for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) { + if (!F(P.atIndex(I), ElemT, Offset)) + return false; + Offset += ElemSize; + } + return true; + } + + // Composite arrays. + if (FieldDesc->isCompositeArray()) { + QualType ElemType = + FieldDesc->getType()->getAsArrayTypeUnsafe()->getElementType(); + size_t ElemSize = + Ctx.getASTContext().getTypeSizeInChars(ElemType).getQuantity(); + for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) { + enumerateData(P.atIndex(I).narrow(), Ctx, Offset, F); + Offset += ElemSize; + } + return true; + } + + // Records. + if (FieldDesc->isRecord()) { + const Record *R = FieldDesc->ElemRecord; + const ASTRecordLayout &Layout = + Ctx.getASTContext().getASTRecordLayout(R->getDecl()); + for (const auto &B : R->bases()) { + Pointer Elem = P.atField(B.Offset); + size_t ByteOffset = + Offset + + Layout.getBaseClassOffset(cast(B.Decl)).getQuantity(); + if (!enumerateData(Elem, Ctx, ByteOffset, F)) + return false; + } + // TODO: Virtual bases? + + for (unsigned I = 0; I != R->getNumFields(); ++I) { + Pointer Elem = P.atField(R->getField(I)->Offset); + CharUnits FieldOffset = + Ctx.getASTContext().toCharUnitsFromBits(Layout.getFieldOffset(I)); + size_t ByteOffset = Offset + FieldOffset.getQuantity(); + if (!enumerateData(Elem, Ctx, ByteOffset, F)) + return false; + } + return true; + } + + llvm_unreachable("Unhandled data type"); +} + +static bool enumeratePointerFields(const Pointer &P, const Context &Ctx, + DataFunc F) { + return enumerateData(P, Ctx, 0, F); +} + +// This function is constexpr if and only if To, From, and the types of +// all subobjects of To and From are types T such that... +// (3.1) - is_union_v is false; +// (3.2) - is_pointer_v is false; +// (3.3) - is_member_pointer_v is false; +// (3.4) - is_volatile_v is false; and +// (3.5) - T has no non-static data members of reference type +// +// NOTE: This is a version of checkBitCastConstexprEligibilityType() in +// ExprConstant.cpp. +static bool CheckBitcastType(InterpState &S, CodePtr OpPC, QualType T, + bool IsToType) { + enum { + E_Union = 0, + E_Pointer, + E_MemberPointer, + E_Volatile, + E_Reference, + }; + enum { C_Member, C_Base }; + + auto diag = [&](int Reason) -> bool { + const Expr *E = S.Current->getExpr(OpPC); + S.FFDiag(E, diag::note_constexpr_bit_cast_invalid_type) + << static_cast(IsToType) << (Reason == E_Reference) << Reason + << E->getSourceRange(); + return false; + }; + auto note = [&](int Construct, QualType NoteType, SourceRange NoteRange) { + S.Note(NoteRange.getBegin(), diag::note_constexpr_bit_cast_invalid_subtype) + << NoteType << Construct << T << NoteRange; + return false; + }; + + T = T.getCanonicalType(); + + if (T->isUnionType()) + return diag(E_Union); + if (T->isPointerType()) + return diag(E_Pointer); + if (T->isMemberPointerType()) + return diag(E_MemberPointer); + if (T.isVolatileQualified()) + return diag(E_Volatile); + + if (const RecordDecl *RD = T->getAsRecordDecl()) { + if (const auto *CXXRD = dyn_cast(RD)) { + for (const CXXBaseSpecifier &BS : CXXRD->bases()) { + if (!CheckBitcastType(S, OpPC, BS.getType(), IsToType)) + return note(C_Base, BS.getType(), BS.getBeginLoc()); + } + } + for (const FieldDecl *FD : RD->fields()) { + if (FD->getType()->isReferenceType()) + return diag(E_Reference); + if (!CheckBitcastType(S, OpPC, FD->getType(), IsToType)) + return note(C_Member, FD->getType(), FD->getSourceRange()); + } + } + + if (T->isArrayType() && + !CheckBitcastType(S, OpPC, S.getCtx().getBaseElementType(T), IsToType)) + return false; + + return true; +} + +/// Bitcast all fields from \p P into \p Buff. +/// This is used for bitcasting TO a single primitive value. +bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff, + size_t BuffSize, unsigned &IndeterminateBits) { + assert(P.isLive()); + assert(Buff); + assert(BuffSize > 0); + + BitcastBuffer F(Buff, BuffSize, S.getCtx().getTargetInfo().isBigEndian()); + + if (!CheckBitcastType(S, OpPC, P.getType(), /*IsToType=*/false)) + return false; + + const Context &Ctx = S.getContext(); + const ASTContext &ASTCtx = Ctx.getASTContext(); + uint64_t PointerSize = + ASTCtx + .toCharUnitsFromBits( + ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default)) + .getQuantity(); + + bool Success = enumeratePointerFields( + P, S.getContext(), + [&](const Pointer &Ptr, PrimType T, size_t ByteOffset) -> bool { + if (!Ptr.isInitialized()) + return false; + if (T == PT_Ptr) { + assert(Ptr.getType()->isNullPtrType()); + std::byte *M = F.getBytes(ByteOffset, PointerSize); + std::memset(M, 0, PointerSize); + return true; + } + + BITCAST_TYPE_SWITCH_WITH_FLOAT(T, { + T Val = Ptr.deref(); + unsigned ObjectReprBytes = + ASTCtx.getTypeSizeInChars(Ptr.getType()).getQuantity(); + std::byte *M = F.getBytes(ByteOffset, ObjectReprBytes); + Val.bitcastToMemory(M); + }); + return true; + }); + + IndeterminateBits = F.IndeterminateBits; + return Success; +} + +// This function is constexpr if and only if To, From, and the types of +// all subobjects of To and From are types T such that... +// (3.1) - is_union_v is false; +// (3.2) - is_pointer_v is false; +// (3.3) - is_member_pointer_v is false; +// (3.4) - is_volatile_v is false; and +// (3.5) - T has no non-static data members of reference type +bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, + CodePtr OpPC) { + assert(P.isLive()); + assert(DestPtr.isLive()); + + QualType FromType = P.getType(); + QualType ToType = DestPtr.getType(); + + if (!CheckBitcastType(S, OpPC, FromType, /*IsToType=*/false)) + return false; + + if (!CheckBitcastType(S, OpPC, ToType, /*IsToType=*/true)) + return false; + + const Context &Ctx = S.getContext(); + const ASTContext &ASTCtx = Ctx.getASTContext(); + uint64_t PointerSize = + ASTCtx + .toCharUnitsFromBits( + ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default)) + .getQuantity(); + bool BigEndian = ASTCtx.getTargetInfo().isBigEndian(); + + ByteTracker Bytes; + enumeratePointerFields( + P, S.getContext(), + [&](const Pointer &P, PrimType T, size_t ByteOffset) -> bool { + bool PtrInitialized = P.isInitialized(); + if (!PtrInitialized) { + Bytes.markUninitializedUntil(ByteOffset + primSize(T)); + return true; + } + + assert(P.isInitialized()); + // nullptr_t is a PT_Ptr for us, but it's still not std::is_pointer_v. + if (T == PT_Ptr) { + assert(P.getType()->isNullPtrType()); + std::byte *M = Bytes.getWritableBytes(ByteOffset, PointerSize, + /*InitValue=*/true); + std::memset(M, 0, PointerSize); + return true; + } + BITCAST_TYPE_SWITCH_WITH_FLOAT(T, { + T Val = P.deref(); + unsigned ObjectReprBytes = + ASTCtx.getTypeSizeInChars(P.getType()).getQuantity(); + unsigned ValueReprBytes = Val.valueReprBytes(ASTCtx); + assert(ObjectReprBytes >= ValueReprBytes); + + std::byte *Dest = Bytes.getWritableBytes(ByteOffset, ValueReprBytes, + PtrInitialized); + Val.bitcastToMemory(Dest); + Bytes.zeroUntil(ByteOffset + ObjectReprBytes); + + if (BigEndian) + swapBytes(Dest, ValueReprBytes); + }); + return true; + }); + + bool Success = enumeratePointerFields( + DestPtr, S.getContext(), + [&](const Pointer &P, PrimType T, size_t ByteOffset) -> bool { + if (T == PT_Float) { + const QualType FloatType = P.getFieldDesc()->getType(); + const auto &Sem = ASTCtx.getFloatTypeSemantics(FloatType); + size_t ValueReprBytes = + ASTCtx.toCharUnitsFromBits(APFloat::semanticsSizeInBits(Sem)) + .getQuantity(); + + std::byte *M = Bytes.getBytes(ByteOffset); + + if (BigEndian) + swapBytes(M, ValueReprBytes); + P.deref() = Floating::bitcastFromMemory(M, Sem); + P.initialize(S); + return true; + } + if (T == PT_Ptr) { + assert(P.getType()->isNullPtrType()); + // Just need to write out a nullptr. + P.deref() = Pointer(); + P.initialize(S); + return true; + } + + BITCAST_TYPE_SWITCH(T, { + T &Val = P.deref(); + + size_t ValueReprBytes = T::valueReprBytes(ASTCtx); + // Check if any of the bits we're about to read are uninitialized. + bool HasIndeterminateBytes = + !Bytes.allInitialized(ByteOffset, ValueReprBytes); + + if (HasIndeterminateBytes) { + // Always an error, unless the type of the field we're reading is + // either unsigned char or std::byte. + bool TargetIsUCharOrBytes = + (ValueReprBytes == 1 && + (P.getType()->isSpecificBuiltinType(BuiltinType::UChar) || + P.getType()->isSpecificBuiltinType(BuiltinType::Char_U) || + P.getType()->isStdByteType())); + + if (!TargetIsUCharOrBytes) { + const Expr *E = S.Current->getExpr(OpPC); + QualType ExprType = P.getType(); + S.FFDiag(E, diag::note_constexpr_bit_cast_indet_dest) + << ExprType << S.getLangOpts().CharIsSigned + << E->getSourceRange(); + return false; + } + } + + std::byte *M = Bytes.getBytes(ByteOffset); + if (BigEndian) + swapBytes(M, ValueReprBytes); + Val = T::bitcastFromMemory(M); + + if (!HasIndeterminateBytes) + P.initialize(S); + }); + return true; + }); + + return Success; +} +} // namespace interp +} // namespace clang Index: clang/lib/AST/Interp/Opcodes.td =================================================================== --- clang/lib/AST/Interp/Opcodes.td +++ clang/lib/AST/Interp/Opcodes.td @@ -556,6 +556,23 @@ let HasGroup = 1; } +def BitCastTypeClass : TypeClass { + let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, Bool]; +} + +def BitCast : Opcode { + let Types = [BitCastTypeClass]; + let HasGroup = 1; + let Args = [ArgBool]; +} + +def BitCastPtr : Opcode; + +def BitCastFP : Opcode { + let Types = []; + let Args = [ArgFltSemantics, ArgUint32]; +} + def CastFP : Opcode { let Types = []; let Args = [ArgFltSemantics, ArgRoundingMode]; Index: clang/lib/AST/Interp/PrimType.h =================================================================== --- clang/lib/AST/Interp/PrimType.h +++ clang/lib/AST/Interp/PrimType.h @@ -78,6 +78,10 @@ /// Returns the size of a primitive type in bytes. size_t primSize(PrimType Type); +template constexpr size_t primSize() { + return sizeof(typename PrimConv::T); +} + /// Aligns a size to the pointer alignment. constexpr size_t align(size_t Size) { return ((Size + alignof(void *) - 1) / alignof(void *)) * alignof(void *); @@ -114,6 +118,7 @@ TYPE_SWITCH_CASE(PT_FnPtr, B) \ } \ } while (0) + #define COMPOSITE_TYPE_SWITCH(Expr, B, D) \ do { \ switch (Expr) { \ Index: clang/test/AST/Interp/builtin-bit-cast.cpp =================================================================== --- /dev/null +++ clang/test/AST/Interp/builtin-bit-cast.cpp @@ -0,0 +1,683 @@ +// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter %s +// RUN: %clang_cc1 -verify=ref -std=c++2a -fsyntax-only %s +// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -triple aarch64_be-linux-gnu -fexperimental-new-constant-interpreter %s +// RUN: %clang_cc1 -verify=ref -std=c++2a -fsyntax-only -triple aarch64_be-linux-gnu %s +// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter -triple powerpc64le-unknown-unknown -mabi=ieeelongdouble %s +// RUN: %clang_cc1 -verify=ref -std=c++2a -fsyntax-only -triple powerpc64le-unknown-unknown -mabi=ieeelongdouble %s +// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter -triple powerpc64-unknown-unknown -mabi=ieeelongdouble %s +// RUN: %clang_cc1 -verify=ref -std=c++2a -fsyntax-only -triple powerpc64-unknown-unknown -mabi=ieeelongdouble %s + +/// FIXME: This is a version of +/// clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp with the currently +/// supported subset of operations. They should *all* be supported though. + + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define LITTLE_END 1 +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define LITTLE_END 0 +#else +# error "huh?" +#endif + +typedef decltype(nullptr) nullptr_t; + +static_assert(sizeof(int) == 4); +static_assert(sizeof(long long) == 8); + +template +constexpr To bit_cast(const From &from) { + static_assert(sizeof(To) == sizeof(From)); + return __builtin_bit_cast(To, from); // ref-note 2{{indeterminate value can only initialize}} \ + // expected-note 2{{indeterminate value can only initialize}} +} + +template +constexpr bool round_trip(const Init &init) { + return bit_cast(bit_cast(init)) == init; +} + +/// We can ignore it. +constexpr int foo() { + (void)__builtin_bit_cast(int, 3); + return 1; +} +static_assert(foo() == 1, ""); + +namespace Ints { + 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(round_trip((int)0x0C05FEFE)); +} + +namespace FloatToDouble { + constexpr float F1[] = {1.0f, 2.0f}; + constexpr double D1 = __builtin_bit_cast(double, F1); + static_assert(D1 > 0); +} + +namespace Arrays { + constexpr unsigned char input[] = {0xCA, 0xFE, 0xBA, 0xBE}; + constexpr unsigned expected = LITTLE_END ? 0xBEBAFECA : 0xCAFEBABE; + static_assert(bit_cast(input) == expected); + + constexpr short S[] = {10, 20}; + constexpr int I = __builtin_bit_cast(int, S); + static_assert(I == (LITTLE_END ? 1310730 : 655380)); +} + +struct int_splicer { + unsigned x; + unsigned y; + + constexpr int_splicer() : x(1), y(2) {} + constexpr int_splicer(unsigned x, unsigned y) : x(x), y(y) {} + + constexpr bool operator==(const int_splicer &other) const { + return other.x == x && other.y == y; + } +}; + +constexpr int_splicer splice(0x0C05FEFE, 0xCAFEBABE); + +static_assert(bit_cast(splice) == (LITTLE_END + ? 0xCAFEBABE0C05FEFE + : 0x0C05FEFECAFEBABE)); + +constexpr int_splicer IS = bit_cast(0xCAFEBABE0C05FEFE); +static_assert(bit_cast(0xCAFEBABE0C05FEFE).x == (LITTLE_END + ? 0x0C05FEFE + : 0xCAFEBABE)); + +static_assert(round_trip(splice)); +static_assert(round_trip(splice)); + +struct base2 { +}; + +struct base3 { + unsigned z; + constexpr base3() : z(3) {} +}; + +struct bases : int_splicer, base2, base3 { + unsigned doublez; + constexpr bases() : doublez(4) {} +}; + +struct tuple4 { + unsigned x, y, z, doublez; + + constexpr bool operator==(tuple4 const &other) const { + return x == other.x && y == other.y && + z == other.z && doublez == other.doublez; + } +}; +constexpr bases b;// = {{1, 2}, {}, {3}, 4}; +constexpr tuple4 t4 = bit_cast(b); + +// Regardless of endianness, this should hold: +static_assert(t4.x == 1); +static_assert(t4.y == 2); +static_assert(t4.z == 3); +static_assert(t4.doublez == 4); +static_assert(t4 == tuple4{1, 2, 3, 4}); +static_assert(round_trip(b)); + +namespace WithBases { + struct Base { + char A[3] = {1,2,3}; + }; + + struct A : Base { + char B = 12; + }; + + constexpr A a; + constexpr unsigned I = __builtin_bit_cast(unsigned, a); + static_assert(I == (LITTLE_END ? 201523713 : 16909068)); +}; + + + +void test_array() { + constexpr unsigned char input[] = {0xCA, 0xFE, 0xBA, 0xBE}; + constexpr unsigned expected = LITTLE_END ? 0xBEBAFECA : 0xCAFEBABE; + static_assert(bit_cast(input) == expected); +} + + +namespace test_array_fill { + constexpr unsigned char a[4] = {1, 2}; + constexpr unsigned int i = bit_cast(a); + static_assert(i == (LITTLE_END ? 0x00000201 : 0x01020000)); +} + +namespace Another { + constexpr char C[] = {1,2,3,4}; + struct F{ short a; short b; }; + constexpr F f = __builtin_bit_cast(F, C); + +#if LITTLE_END + static_assert(f.a == 513); + static_assert(f.b == 1027); +#else + static_assert(f.a == 258); + static_assert(f.b == 772); +#endif +} + + +namespace array_members { + struct S { + int ar[3]; + + constexpr bool operator==(const S &rhs) { + return ar[0] == rhs.ar[0] && ar[1] == rhs.ar[1] && ar[2] == rhs.ar[2]; + } + }; + + struct G { + int a, b, c; + + constexpr bool operator==(const G &rhs) { + return a == rhs.a && b == rhs.b && c == rhs.c; + } + }; + + constexpr S s{{1, 2, 3}}; + 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)); +} + +namespace CompositeArrays { + struct F { + int a; + int b; + }; + + static_assert(sizeof(long) == 2 * sizeof(int)); + + constexpr F ff[] = {{1,2}}; + constexpr long L = __builtin_bit_cast(long, ff); + +#if LITTLE_END + static_assert(L == 8589934593); +#else + static_assert(L == 4294967298); +#endif +} + + + +#ifdef __CHAR_UNSIGNED__ +// ref-note@+5 {{indeterminate value can only initialize an object of type 'unsigned char', 'char', or 'std::byte'; 'unsigned long' is invalid}} +#else +// ref-note@+3 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'unsigned long' is invalid}} +#endif +// ref-error@+1 {{constexpr variable 'test_from_nullptr' must be initialized by a constant expression}} +constexpr unsigned long test_from_nullptr = __builtin_bit_cast(unsigned long, nullptr); + +constexpr int test_from_nullptr_pass = (__builtin_bit_cast(unsigned char[8], nullptr), 0); + +constexpr int test_to_nullptr() { + nullptr_t npt = __builtin_bit_cast(nullptr_t, 0ul); + + struct indet_mem { + unsigned char data[sizeof(void *)]; + }; + indet_mem im = __builtin_bit_cast(indet_mem, nullptr); + nullptr_t npt2 = __builtin_bit_cast(nullptr_t, im); + + return 0; +} + +constexpr int ttn = test_to_nullptr(); + +namespace IndeterminateToPrimitive { + struct S { + bool a; + // One byte of padding. + short b; + }; + constexpr S s{true, 12}; + + static_assert(sizeof(S) == sizeof(int), ""); + constexpr int A = __builtin_bit_cast(int, s); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{indeterminate value}} \ + // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{indeterminate value}} +} + +namespace test_partially_initialized { + struct pad { + signed char x; + int y; + }; + + struct no_pad { + signed char x; + signed char p1, p2, p3; + int y; + }; + + static_assert(sizeof(pad) == sizeof(no_pad)); + + 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; + // ref-error@-1 {{constexpr variable 'piw' must be initialized by a constant expression}} + // ref-note@-2 {{in call to 'bit_cast(pir)'}} + + // 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); + // ref-error@-1 {{constexpr variable 'bad' must be initialized by a constant expression}} + // ref-note@-2 {{in call to 'bit_cast(pir)'}} + + constexpr pad fine = bit_cast(no_pad{1, 2, 3, 4, 5}); + static_assert(fine.x == 1 && fine.y == 5); +} + +namespace bad_types { + union X { + int x; + }; + + struct G { + int g; + }; + // ref-error@+2 {{constexpr variable 'g' must be initialized by a constant expression}} + // ref-note@+1 {{bit_cast from a union type is not allowed in a constant expression}} + constexpr G g = __builtin_bit_cast(G, X{0}); + // expected-error@-1 {{constexpr variable 'g' must be initialized by a constant expression}} + // expected-note@-2 {{bit_cast from a union type is not allowed in a constant expression}} + + // ref-error@+2 {{constexpr variable 'x' must be initialized by a constant expression}} + // ref-note@+1 {{bit_cast to a union type is not allowed in a constant expression}} + constexpr X x = __builtin_bit_cast(X, G{0}); + // expected-error@-1 {{constexpr variable 'x' must be initialized by a constant expression}} + // expected-note@-2 {{bit_cast to a union type is not allowed in a constant expression}} + + struct has_pointer { + int *ptr; // ref-note 2{{invalid type 'int *' is a member of}} \ + // expected-note 2{{invalid type 'int *' is a member of}} + }; + + // ref-error@+2 {{constexpr variable 'ptr' must be initialized by a constant expression}} + // ref-note@+1 {{bit_cast from a pointer type is not allowed in a constant expression}} + constexpr unsigned long ptr = __builtin_bit_cast(unsigned long, has_pointer{0}); + // expected-error@-1 {{constexpr variable 'ptr' must be initialized by a constant expression}} + // expected-note@-2 {{bit_cast from a pointer type is not allowed in a constant expression}} + + + // ref-error@+2 {{constexpr variable 'hptr' must be initialized by a constant expression}} + // ref-note@+1 {{bit_cast to a pointer type is not allowed in a constant expression}} + constexpr has_pointer hptr = __builtin_bit_cast(has_pointer, 0ul); + // expected-error@-1 {{constexpr variable 'hptr' must be initialized by a constant expression}} + // expected-note@-2 {{bit_cast to a pointer type is not allowed in a constant expression}} +} + +namespace backtrace { + struct A { + int *ptr; // expected-note {{invalid type 'int *' is a member of 'backtrace::A'}} \ + // ref-note {{invalid type 'int *' is a member of 'backtrace::A'}} + }; + + struct B { + A as[10]; // expected-note {{invalid type 'A[10]' is a member of 'backtrace::B'}} \ + // ref-note {{invalid type 'A[10]' is a member of 'backtrace::B'}} + }; + + struct C : B { // expected-note {{invalid type 'B' is a base of 'backtrace::C'}} \ + // ref-note {{invalid type 'B' is a base of 'backtrace::C'}} + }; + + struct E { + unsigned long ar[10]; + }; + + constexpr E e = __builtin_bit_cast(E, C{}); // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{bit_cast from a pointer type is not allowed}} \ + // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{bit_cast from a pointer type is not allowed}} +} + +namespace ReferenceMember { + struct ref_mem { + const int &rm; + }; + + typedef unsigned long ulong; + constexpr int global_int = 0; + + constexpr ulong run_ref_mem = __builtin_bit_cast(ulong, ref_mem{global_int}); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{bit_cast from a type with a reference member}} \ + // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{bit_cast from a type with a reference member}} +} + +namespace FromUnion { + union u { + int im; + }; + + constexpr int run_u = __builtin_bit_cast(int, u{32}); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{bit_cast from a union type}} \ + // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{bit_cast from a union type}} +} + + +struct vol_mem { + volatile int x; // expected-note {{invalid type 'volatile int' is a member of 'vol_mem'}} +}; + +namespace VolatileMember { + constexpr int run_vol_mem = __builtin_bit_cast(int, vol_mem{43}); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{non-literal type 'vol_mem'}} \ + // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{bit_cast from a volatile type}} +} + +namespace MemberPointer { + /// FIXME: The diagnostic for bitcasts is properly implemented, but we lack support for member pointers. +#if 0 + struct mem_ptr { + int vol_mem::*x; // expected-note{{invalid type 'int vol_mem::*' is a member of 'mem_ptr'}} + }; + constexpr int run_mem_ptr = __builtin_bit_cast(unsigned long, mem_ptr{nullptr}); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{bit_cast from a member pointer type}} \ + // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{bit_cast from a member pointer type}} +#endif +} + + struct A { char c; /* char padding : 8; */ short s; }; + struct B { unsigned char x[4]; }; + + constexpr B one() { + A a = {1, 2}; + return bit_cast(a); + } + constexpr char good_one = one().x[0] + one().x[2] + one().x[3]; + // ref-error@+2 {{constexpr variable 'bad_one' must be initialized by a constant expression}} + // ref-note@+1 {{read of uninitialized object is not allowed in a constant expression}} + constexpr char bad_one = one().x[1]; + // expected-error@-1 {{constexpr variable 'bad_one' must be initialized by a constant expression}} + // expected-note@-2 {{read of uninitialized object is not allowed in a constant expression}} + + + constexpr A two() { + B b = one(); // b.x[1] is indeterminate. + b.x[0] = 'a'; + b.x[2] = 1; + b.x[3] = 2; + return bit_cast(b); + } + constexpr short good_two = two().c + two().s; + + + namespace std { + enum byte : unsigned char {}; + } + + enum my_byte : unsigned char {}; + + struct pad { + char a; + int b; + }; + + 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); + +#ifdef __CHAR_UNSIGNED__ + // ref-note@+5 {{indeterminate value can only initialize an object of type 'unsigned char', 'char', or 'std::byte'; 'my_byte' is invalid}}}} +#else + // ref-note@+3 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'my_byte' is invalid}} +#endif + // ref-error@+1 {{must be initialized by a constant expression}} + constexpr int bad_my_byte = (__builtin_bit_cast(my_byte[8], pad{1, 2}), 0); // {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'my_byte' is invalid}} + +#ifndef __CHAR_UNSIGNED__ + // ref-error@+3 {{must be initialized by a constant expression}} + // ref-note@+2 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'char' is invalid}} +#endif + constexpr int bad_char = (__builtin_bit_cast(char[8], pad{1, 2}), 0); + + struct pad_buffer { unsigned char data[sizeof(pad)]; }; + constexpr bool test_pad_buffer() { + pad x = {1, 2}; + pad_buffer y = __builtin_bit_cast(pad_buffer, x); + pad z = __builtin_bit_cast(pad, y); + return x.a == z.a && x.b == z.b; + } + static_assert(test_pad_buffer()); + +namespace ValueRepr { + /// FIXME: This is broken. + constexpr bool b = __builtin_bit_cast(bool, (char)123); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{value 123 cannot be represented in type 'bool'}} +} + +namespace FloatMember { + struct A { + float a; + }; + struct B { + unsigned char a[4]; + }; + + constexpr B b = __builtin_bit_cast(B, A{1.0}); + +#if LITTLE_END + static_assert(b.a[0] == 0, ""); + static_assert(b.a[1] == 0, ""); + static_assert(b.a[2] == 128, ""); + static_assert(b.a[3] == 63, ""); +#else + static_assert(b.a[0] == 63, ""); + static_assert(b.a[1] == 128, ""); + static_assert(b.a[2] == 0, ""); + static_assert(b.a[3] == 0, ""); +#endif + + constexpr A a = __builtin_bit_cast(A, B{{1 << 6, 0, 0, 1 << 6}}); + static_assert(static_cast(a.a) == 2, ""); +} + +namespace Misc { + struct A { + decltype(nullptr) a; + }; + /// Not sure why this doesn't work in the current interpreter; GCC accepts it. + constexpr unsigned long long L = __builtin_bit_cast(unsigned long long, A{nullptr}); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{indeterminate value}} + + /// Bitcast into a nullptr_t field. + struct B { + unsigned char a[8]; + }; + + constexpr A a = __builtin_bit_cast(A, B{{0, 0, 0, 0, 0, 0, 0, 0}}); + static_assert(a.a == nullptr, ""); + + /// Uninitialized local variable bitcast'ed to a uchar. + constexpr int primUChar() { + signed char A; + unsigned char B = __builtin_bit_cast(unsigned char, A); + /// FIXME: The new interpreter doesn't print the proper diagnostic here; The read from B + /// should be uninitialized, since the bitcast returns indeterminate bits. However, + /// the code uses primitive values and those are always initialized. + return B; // ref-note {{read of uninitialized object}} + } + static_assert(primUChar() == 0, ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{in call to}} \ + // expected-error {{not an integral constant expression}} + + constexpr int primUChar2() { + signed char A; + unsigned char B = __builtin_bit_cast(unsigned char, A) + 1; + /// Same problem as above, but the diagnostic of the current interpreter is just as bad as ours. + return B; + } + static_assert(primUChar2() == 0, ""); // ref-error {{not an integral constant expression}} \ + // expected-error {{not an integral constant expression}} + + /// This time, the uchar is in a struct. + constexpr int primUChar3() { + struct B { + unsigned char b; + }; + signed char A; + B b = __builtin_bit_cast(B, A); + return b.b; // ref-note {{read of uninitialized object}} \ + // expected-note {{read of uninitialized object}} + } + static_assert(primUChar3() == 0, ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{in call to}} \ + // expected-error {{not an integral constant expression}} \ + // expected-note {{in call to}} + + /// Fully initialized local variable that should end up being un-initalized again because the + /// bit cast returns bits of indeterminate value. + constexpr int primUChar4() { + unsigned char c = 'a'; + signed char cu; + + c = __builtin_bit_cast(unsigned char, cu); + + return c; // ref-note {{read of uninitialized object}} + } + static_assert(primUChar4() == 0, ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{in call to}} \ + // expected-error {{not an integral constant expression}} + + + /// Casting an uninitialized struct. + constexpr int primUChar5(bool DoBC) { + struct A { + unsigned char a; // ref-note {{subobject declared here}} + }; + struct B { + signed char b; + }; + + A a = {12}; + + if (DoBC) { + B b; + a = __builtin_bit_cast(A, b); // expected-note {{in call to}} \ + // expected-note {{read of uninitialized object}} \ + // ref-note {{subobject 'a' is not initialized}} \ + // ref-note {{in call to}} + } + + return a.a; + } + static_assert(primUChar5(false) == 12, ""); + static_assert(primUChar5(true) == 12, ""); // expected-error {{not an integral constant expression}} \ + // expected-note {{in call to 'primUChar5(true)'}} \ + // ref-error {{not an integral constant expression}} \ + // ref-note {{in call to 'primUChar5(true)'}} +} + + +constexpr unsigned char identity1a = 42; +constexpr unsigned char identity1b = __builtin_bit_cast(unsigned char, identity1a); +static_assert(identity1b == 42); + +#ifdef __PPC64__ +namespace LongDouble { + struct bytes { + unsigned char d[sizeof(long double)]; + }; + + constexpr long double ld = 3.1425926539; + constexpr long double ldmax = __LDBL_MAX__; + static_assert(round_trip(ld), ""); + static_assert(round_trip(ldmax), ""); + + constexpr bytes b = __builtin_bit_cast(bytes, ld); + +#if LITTLE_END +static_assert(b.d[0] == 0); +static_assert(b.d[1] == 0); +static_assert(b.d[2] == 0); +static_assert(b.d[3] == 0); +static_assert(b.d[4] == 0); +static_assert(b.d[5] == 0); + +static_assert(b.d[6] == 0); +static_assert(b.d[7] == 144); +static_assert(b.d[8] == 62); +static_assert(b.d[9] == 147); +static_assert(b.d[10] == 224); +static_assert(b.d[11] == 121); +static_assert(b.d[12] == 64); +static_assert(b.d[13] == 146); +static_assert(b.d[14] == 0); +static_assert(b.d[15] == 64); +#else +static_assert(b.d[0] == 64); +static_assert(b.d[1] == 0); +static_assert(b.d[2] == 146); +static_assert(b.d[3] == 64); +static_assert(b.d[4] == 121); +static_assert(b.d[5] == 224); +static_assert(b.d[6] == 147); +static_assert(b.d[7] == 62); +static_assert(b.d[8] == 144); +static_assert(b.d[9] == 0); + +static_assert(b.d[10] == 0); +static_assert(b.d[11] == 0); +static_assert(b.d[12] == 0); +static_assert(b.d[13] == 0); +static_assert(b.d[14] == 0); +static_assert(b.d[15] == 0); +#endif +} +#endif // __PPC64__ + +#ifdef __x86_64 +namespace LongDoubleX86 { + struct bytes { + unsigned char d[16]; // ref-note {{declared here}} + }; + + constexpr long double ld = 3.1425926539; + static_assert(round_trip(ld), ""); + + /// The current interpreter rejects this (probably because the APFloat only + /// uses 10 bytes to represent the value instead of the full 16 that the long double + /// takes up). MSVC and GCC accept it though. + constexpr bytes b = __builtin_bit_cast(bytes, ld); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{subobject 'd' is not initialized}} +} +#endif + +namespace StringLiterals { + template + struct StrBuff { + char data[n]; + }; + + constexpr StrBuff<4> Foo = __builtin_bit_cast(StrBuff<4>, "foo"); // ref-error {{must be initialized by a constant expression}} \ + // ref-note 4{{declared here}} + static_assert(Foo.data[0] == 'f', ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'Foo' is not a constant expression}} + static_assert(Foo.data[1] == 'o', ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'Foo' is not a constant expression}} + static_assert(Foo.data[2] == 'o', ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'Foo' is not a constant expression}} + static_assert(Foo.data[3] == '\0', ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'Foo' is not a constant expression}} +}; Index: clang/test/AST/Interp/literals.cpp =================================================================== --- clang/test/AST/Interp/literals.cpp +++ clang/test/AST/Interp/literals.cpp @@ -98,6 +98,11 @@ static_assert(p != nullptr, ""); static_assert(*p == 10, ""); +constexpr const void *cp = (void *)p; +// FIXME: This should be an error in the new interpreter. +constexpr const int *pm = (int*)cp; // ref-error {{ must be initialized by a constant expression}} \ + // ref-note {{cast from 'const void *' is not allowed}} + constexpr const int* getIntPointer() { return &m; }