diff --git a/clang/lib/AST/ByteCode/Boolean.h b/clang/lib/AST/ByteCode/Boolean.h index c568b557574e2b..78d75e75c7531a 100644 --- a/clang/lib/AST/ByteCode/Boolean.h +++ b/clang/lib/AST/ByteCode/Boolean.h @@ -81,6 +81,16 @@ class Boolean final { Boolean truncate(unsigned TruncBits) const { return *this; } + static Boolean bitcastFromMemory(const std::byte *Buff, unsigned BitWidth) { + // Boolean width is currently always 8 for all supported targets. If this + // changes we need to get the bool width from the target info. + assert(BitWidth == 8); + bool Val = static_cast(*Buff); + return Boolean(Val); + } + + void bitcastToMemory(std::byte *Buff) { std::memcpy(Buff, &V, sizeof(V)); } + void print(llvm::raw_ostream &OS) const { OS << (V ? "true" : "false"); } std::string toDiagnosticString(const ASTContext &Ctx) const { std::string NameStr; diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index 8ca63bf64aa0ef..84cbbd8426eed2 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -470,6 +470,9 @@ bool Compiler::VisitCastExpr(const CastExpr *CE) { return this->emitDecayPtr(*FromT, *ToT, CE); } + case CK_LValueToRValueBitCast: + return this->emitBuiltinBitCast(CE); + case CK_IntegralToBoolean: case CK_FixedPointToBoolean: case CK_BooleanToSignedIntegral: @@ -6409,6 +6412,67 @@ bool Compiler::emitDestruction(const Descriptor *Desc, return this->emitRecordDestruction(Desc->ElemRecord, Loc); } +// 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 Compiler::emitBuiltinBitCast(const CastExpr *E) { + const Expr *SubExpr = E->getSubExpr(); + QualType FromType = SubExpr->getType(); + QualType ToType = E->getType(); + std::optional ToT = classify(ToType); + + assert(!DiscardResult && "Implement DiscardResult mode for bitcasts."); + + if (ToType->isNullPtrType()) { + if (!this->discard(SubExpr)) + return false; + + return this->emitNullPtr(nullptr, E); + } + + if (FromType->isNullPtrType() && ToT) { + if (!this->discard(SubExpr)) + return false; + + return visitZeroInitializer(*ToT, 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. + assert(false && "Implement bitcast to pointers."); + } + assert(ToT); + + const llvm::fltSemantics *TargetSemantics = nullptr; + if (ToT == PT_Float) + TargetSemantics = &Ctx.getFloatSemantics(ToType); + + // Conversion to a primitive type. FromType can be another + // primitive type, or a record/array. + bool ToTypeIsUChar = (ToType->isSpecificBuiltinType(BuiltinType::UChar) || + ToType->isSpecificBuiltinType(BuiltinType::Char_U)); + uint32_t ResultBitWidth = std::max(Ctx.getBitWidth(ToType), 8u); + + if (!this->emitBitCast(*ToT, ToTypeIsUChar || ToType->isStdByteType(), + ResultBitWidth, TargetSemantics, E)) + return false; + + if (DiscardResult) + return this->emitPop(*ToT, E); + + return true; +} + namespace clang { namespace interp { diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h index 4253e7b3248c9f..5fbde244b1d9e9 100644 --- a/clang/lib/AST/ByteCode/Compiler.h +++ b/clang/lib/AST/ByteCode/Compiler.h @@ -373,6 +373,7 @@ class Compiler : public ConstStmtVisitor, bool>, unsigned collectBaseOffset(const QualType BaseType, const QualType DerivedType); bool emitLambdaStaticInvokerBody(const CXXMethodDecl *MD); + bool emitBuiltinBitCast(const CastExpr *E); bool compileConstructor(const CXXConstructorDecl *Ctor); bool compileDestructor(const CXXDestructorDecl *Dtor); diff --git a/clang/lib/AST/ByteCode/Floating.h b/clang/lib/AST/ByteCode/Floating.h index 114487821880fb..be38e6991dad75 100644 --- a/clang/lib/AST/ByteCode/Floating.h +++ b/clang/lib/AST/ByteCode/Floating.h @@ -135,6 +135,11 @@ class Floating final { return Floating(APFloat(Sem, API)); } + void bitcastToMemory(std::byte *Buff) { + llvm::APInt API = F.bitcastToAPInt(); + llvm::StoreIntToMemory(API, (uint8_t *)Buff, bitWidth() / 8); + } + // === Serialization support === size_t bytesToSerialize() const { return sizeof(llvm::fltSemantics *) + diff --git a/clang/lib/AST/ByteCode/Integral.h b/clang/lib/AST/ByteCode/Integral.h index e06ec1669259da..9b07e86bbc8dd9 100644 --- a/clang/lib/AST/ByteCode/Integral.h +++ b/clang/lib/AST/ByteCode/Integral.h @@ -151,6 +151,18 @@ template class Integral final { return Compare(V, RHS.V); } + void bitcastToMemory(std::byte *Dest) const { + std::memcpy(Dest, &V, sizeof(V)); + } + + static Integral bitcastFromMemory(const std::byte *Src, unsigned BitWidth) { + assert(BitWidth == sizeof(ReprT) * 8); + ReprT V; + + std::memcpy(&V, Src, sizeof(ReprT)); + return Integral(V); + } + std::string toDiagnosticString(const ASTContext &Ctx) const { std::string NameStr; llvm::raw_string_ostream OS(NameStr); diff --git a/clang/lib/AST/ByteCode/IntegralAP.h b/clang/lib/AST/ByteCode/IntegralAP.h index a4d656433344b7..58e413e4d06c12 100644 --- a/clang/lib/AST/ByteCode/IntegralAP.h +++ b/clang/lib/AST/ByteCode/IntegralAP.h @@ -173,6 +173,12 @@ template class IntegralAP final { return IntegralAP(Copy); } + void bitcastToMemory(std::byte *Dest) const { assert(false); } + + static IntegralAP bitcastFromMemory(const std::byte *Src, unsigned BitWidth) { + return IntegralAP(); + } + ComparisonCategoryResult compare(const IntegralAP &RHS) const { assert(Signed == RHS.isSigned()); assert(bitWidth() == RHS.bitWidth()); diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index 95715655cc9bbd..0f830076f131af 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1539,6 +1539,23 @@ bool CastPointerIntegralAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth) { return true; } +bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits, + bool TargetIsUCharOrByte) { + // This is always fine. + if (!HasIndeterminateBits) + 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; +} + // https://github.com/llvm/llvm-project/issues/102513 #if defined(_WIN32) && !defined(__clang__) && !defined(NDEBUG) #pragma optimize("", off) diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index dece95971b7617..6001f6809051fa 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -20,6 +20,7 @@ #include "Floating.h" #include "Function.h" #include "FunctionPointer.h" +#include "InterpBuiltinBitCast.h" #include "InterpFrame.h" #include "InterpStack.h" #include "InterpState.h" @@ -162,6 +163,8 @@ bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize, const CallExpr *CE); bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T); bool InvalidShuffleVectorIndex(InterpState &S, CodePtr OpPC, uint32_t Index); +bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits, + bool TargetIsUCharOrByte); template static bool handleOverflow(InterpState &S, CodePtr OpPC, const T &SrcValue) { @@ -2995,6 +2998,31 @@ bool CheckNewTypeMismatchArray(InterpState &S, CodePtr OpPC, const Expr *E) { return CheckNewTypeMismatch(S, OpPC, E, static_cast(Size)); } bool InvalidNewDeleteExpr(InterpState &S, CodePtr OpPC, const Expr *E); + +template ::T> +inline bool BitCast(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte, + uint32_t ResultBitWidth, const llvm::fltSemantics *Sem) { + const Pointer &FromPtr = S.Stk.pop(); + + size_t BuffSize = ResultBitWidth / 8; + llvm::SmallVector Buff(BuffSize); + bool HasIndeterminateBits = false; + + if (!DoBitCast(S, OpPC, FromPtr, Buff.data(), BuffSize, HasIndeterminateBits)) + return false; + + if (!CheckBitCast(S, OpPC, HasIndeterminateBits, TargetIsUCharOrByte)) + return false; + + if constexpr (std::is_same_v) { + assert(false && "Implement bitcasting to a floating type"); + } else { + assert(!Sem); + S.Stk.push(T::bitcastFromMemory(Buff.data(), ResultBitWidth)); + } + return true; +} + //===----------------------------------------------------------------------===// // Read opcode arguments //===----------------------------------------------------------------------===// diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index ec27aebf84bd80..d0d569cb46381f 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -10,6 +10,7 @@ #include "Compiler.h" #include "EvalEmitter.h" #include "Interp.h" +#include "InterpBuiltinBitCast.h" #include "PrimType.h" #include "clang/AST/OSLog.h" #include "clang/AST/RecordLayout.h" diff --git a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp new file mode 100644 index 00000000000000..dfa79c3d7f57d7 --- /dev/null +++ b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp @@ -0,0 +1,353 @@ +//===-------------------- InterpBuiltinBitCast.cpp --------------*- 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 "InterpBuiltinBitCast.h" +#include "Boolean.h" +#include "Context.h" +#include "FixedPoint.h" +#include "Floating.h" +#include "Integral.h" +#include "IntegralAP.h" +#include "InterpState.h" +#include "MemberPointer.h" +#include "Pointer.h" +#include "Record.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/RecordLayout.h" +#include "clang/Basic/TargetInfo.h" +#include "llvm/ADT/BitVector.h" +#include + +using namespace clang; +using namespace clang::interp; + +/// 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_IntAP, B) \ + TYPE_SWITCH_CASE(PT_IntAPS, 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_IntAP, B) \ + TYPE_SWITCH_CASE(PT_IntAPS, B) \ + TYPE_SWITCH_CASE(PT_Bool, B) \ + TYPE_SWITCH_CASE(PT_Float, B) \ + default: \ + llvm_unreachable("Unhandled bitcast type"); \ + } \ + } while (0) + +static bool bitof(std::byte B, unsigned BitIndex) { + return (B & (std::byte{1} << BitIndex)) != std::byte{0}; +} + +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 bits have been initialized to known values and which ones +/// have indeterminate value. +/// All offsets are in bits. +struct BitcastBuffer { + llvm::BitVector Data; + + BitcastBuffer() = default; + + size_t size() const { return Data.size(); } + + const std::byte *data() const { + unsigned NBytes = Data.size() / 8; + unsigned BitVectorWordSize = sizeof(uintptr_t); + bool FullWord = (NBytes % BitVectorWordSize == 0); + + // llvm::BitVector uses 64-bit fields internally, so when we have + // fewer bytes than that, we need to compensate for that on + // big endian hosts. + unsigned DataPlus; + if (llvm::sys::IsBigEndianHost) + DataPlus = BitVectorWordSize - (NBytes % BitVectorWordSize); + else + DataPlus = 0; + + return reinterpret_cast(Data.getData().data()) + + (FullWord ? 0 : DataPlus); + } + + bool allInitialized() const { + // FIXME: Implement. + return true; + } + + void pushData(const std::byte *data, size_t BitOffset, size_t BitWidth, + bool BigEndianTarget) { + Data.reserve(BitOffset + BitWidth); + + bool OnlyFullBytes = BitWidth % 8 == 0; + unsigned NBytes = BitWidth / 8; + + size_t BitsHandled = 0; + // Read all full bytes first + for (size_t I = 0; I != NBytes; ++I) { + std::byte B = + BigEndianTarget ? data[NBytes - OnlyFullBytes - I] : data[I]; + for (unsigned X = 0; X != 8; ++X) { + Data.push_back(bitof(B, X)); + ++BitsHandled; + } + } + + if (BitsHandled == BitWidth) + return; + + // Rest of the bits. + assert((BitWidth - BitsHandled) < 8); + std::byte B = BigEndianTarget ? data[0] : data[NBytes]; + for (size_t I = 0, E = (BitWidth - BitsHandled); I != E; ++I) { + Data.push_back(bitof(B, I)); + ++BitsHandled; + } + + assert(BitsHandled == BitWidth); + } +}; + +/// 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, FieldDesc->getPrimType(), Offset); + + // Primitive arrays. + if (FieldDesc->isPrimitiveArray()) { + bool BigEndianTarget = Ctx.getASTContext().getTargetInfo().isBigEndian(); + QualType ElemType = FieldDesc->getElemQualType(); + size_t ElemSizeInBits = Ctx.getASTContext().getTypeSize(ElemType); + PrimType ElemT = *Ctx.classify(ElemType); + bool Ok = true; + for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) { + unsigned Index = BigEndianTarget ? (FieldDesc->getNumElems() - 1 - I) : I; + Ok = Ok && F(P.atIndex(Index), ElemT, Offset); + Offset += ElemSizeInBits; + } + return Ok; + } + + // Composite arrays. + if (FieldDesc->isCompositeArray()) { + bool BigEndianTarget = Ctx.getASTContext().getTargetInfo().isBigEndian(); + QualType ElemType = FieldDesc->getElemQualType(); + size_t ElemSizeInBits = Ctx.getASTContext().getTypeSize(ElemType); + for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) { + unsigned Index = BigEndianTarget ? (FieldDesc->getNumElems() - 1 - I) : I; + enumerateData(P.atIndex(Index).narrow(), Ctx, Offset, F); + Offset += ElemSizeInBits; + } + return true; + } + + // Records. + if (FieldDesc->isRecord()) { + bool BigEndianTarget = Ctx.getASTContext().getTargetInfo().isBigEndian(); + const Record *R = FieldDesc->ElemRecord; + const ASTRecordLayout &Layout = + Ctx.getASTContext().getASTRecordLayout(R->getDecl()); + bool Ok = true; + for (const auto &B : R->bases()) { + Pointer Elem = P.atField(B.Offset); + CharUnits ByteOffset = + Layout.getBaseClassOffset(cast(B.Decl)); + size_t BitOffset = Offset + Ctx.getASTContext().toBits(ByteOffset); + Ok = Ok && enumerateData(Elem, Ctx, BitOffset, F); + } + + for (unsigned I = 0; I != R->getNumFields(); ++I) { + const Record::Field *Fi = + R->getField(BigEndianTarget ? (R->getNumFields() - 1 - I) : I); + Pointer Elem = P.atField(Fi->Offset); + size_t BitOffset = + Offset + Layout.getFieldOffset(Fi->Decl->getFieldIndex()); + Ok = Ok && enumerateData(Elem, Ctx, BitOffset, F); + } + return Ok; + } + + 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.getASTContext().getBaseElementType(T), + IsToType)) + return false; + + return true; +} + +static bool readPointerToBuffer(const Context &Ctx, const Pointer &FromPtr, + BitcastBuffer &Buffer, bool ReturnOnUninit) { + const ASTContext &ASTCtx = Ctx.getASTContext(); + bool SwapData = (ASTCtx.getTargetInfo().isLittleEndian() != + llvm::sys::IsLittleEndianHost); + bool BigEndianTarget = ASTCtx.getTargetInfo().isBigEndian(); + + return enumeratePointerFields( + FromPtr, Ctx, + [&](const Pointer &P, PrimType T, size_t BitOffset) -> bool { + if (!P.isInitialized()) { + assert(false && "Implement uninitialized value tracking"); + return ReturnOnUninit; + } + + 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(false && "Implement casting to pointer types"); + + CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(P.getType()); + unsigned BitWidth; + if (const FieldDecl *FD = P.getField(); FD && FD->isBitField()) + BitWidth = FD->getBitWidthValue(ASTCtx); + else + BitWidth = ASTCtx.toBits(ObjectReprChars); + + llvm::SmallVector Buff(ObjectReprChars.getQuantity()); + BITCAST_TYPE_SWITCH_WITH_FLOAT(T, { + T Val = P.deref(); + Val.bitcastToMemory(Buff.data()); + }); + if (SwapData) + swapBytes(Buff.data(), ObjectReprChars.getQuantity()); + + if (BitWidth != (Buff.size() * 8) && BigEndianTarget) { + Buffer.pushData(Buff.data() + (Buff.size() - 1 - (BitWidth / 8)), + BitOffset, BitWidth, BigEndianTarget); + } else { + Buffer.pushData(Buff.data(), BitOffset, BitWidth, BigEndianTarget); + } + return true; + }); +} + +bool clang::interp::DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr, + std::byte *Buff, size_t BuffSize, + bool &HasIndeterminateBits) { + assert(Ptr.isLive()); + assert(Ptr.isBlockPointer()); + assert(Buff); + + BitcastBuffer Buffer; + if (!CheckBitcastType(S, OpPC, Ptr.getType(), /*IsToType=*/false)) + return false; + + bool Success = readPointerToBuffer(S.getContext(), Ptr, Buffer, + /*ReturnOnUninit=*/false); + assert(Buffer.size() == BuffSize * 8); + + HasIndeterminateBits = !Buffer.allInitialized(); + std::memcpy(Buff, Buffer.data(), BuffSize); + + return Success; +} diff --git a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.h b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.h new file mode 100644 index 00000000000000..84ba784e95e235 --- /dev/null +++ b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.h @@ -0,0 +1,26 @@ +//===------------------ InterpBuiltinBitCast.h ------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_INTERP_BUILITN_BIT_CAST_H +#define LLVM_CLANG_AST_INTERP_BUILITN_BIT_CAST_H + +#include + +namespace clang { +namespace interp { +class Pointer; +class InterpState; +class CodePtr; + +bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr, + std::byte *Buff, size_t BuffSize, bool &HasIndeterminateBits); + +} // namespace interp +} // namespace clang + +#endif diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td index 4fa9b6d61d5ab9..27ebd5d3dd88d9 100644 --- a/clang/lib/AST/ByteCode/Opcodes.td +++ b/clang/lib/AST/ByteCode/Opcodes.td @@ -836,3 +836,13 @@ def CheckNewTypeMismatchArray : Opcode { } def IsConstantContext: Opcode; + +def BitCastTypeClass : TypeClass { + let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, IntAP, IntAPS, Bool, Float]; +} + +def BitCast : Opcode { + let Types = [BitCastTypeClass]; + let Args = [ArgBool, ArgUint32, ArgFltSemantics]; + let HasGroup = 1; +} diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index 6195a16c2c68db..038856248c1604 100644 --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -74,6 +74,7 @@ add_clang_library(clangAST ByteCode/Function.cpp ByteCode/FunctionPointer.cpp ByteCode/InterpBuiltin.cpp + ByteCode/InterpBuiltinBitCast.cpp ByteCode/Floating.cpp ByteCode/EvaluationResult.cpp ByteCode/DynamicAllocator.cpp diff --git a/clang/test/AST/ByteCode/builtin-bit-cast.cpp b/clang/test/AST/ByteCode/builtin-bit-cast.cpp new file mode 100644 index 00000000000000..64a7be9c878674 --- /dev/null +++ b/clang/test/AST/ByteCode/builtin-bit-cast.cpp @@ -0,0 +1,359 @@ +// RUN: %clang_cc1 -verify=ref,both -std=c++2a -fsyntax-only %s +// RUN: %clang_cc1 -verify=ref,both -std=c++2a -fsyntax-only -triple aarch64_be-linux-gnu %s +// RUN: %clang_cc1 -verify=ref,both -std=c++2a -fsyntax-only -triple powerpc64le-unknown-unknown -mabi=ieeelongdouble %s +// RUN: %clang_cc1 -verify=ref,both -std=c++2a -fsyntax-only -triple powerpc64-unknown-unknown -mabi=ieeelongdouble %s + +// RUN: %clang_cc1 -verify=expected,both -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter %s +// RUN: %clang_cc1 -verify=expected,both -std=c++2a -fsyntax-only -triple aarch64_be-linux-gnu -fexperimental-new-constant-interpreter %s +// RUN: %clang_cc1 -verify=expected,both -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter -triple powerpc64le-unknown-unknown -mabi=ieeelongdouble %s +// RUN: %clang_cc1 -verify=expected,both -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter -triple powerpc64-unknown-unknown -mabi=ieeelongdouble %s + +#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; +typedef __INTPTR_TYPE__ intptr_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); +} + +template +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)); +} + +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]; + } +}; + + +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; +} + + +namespace simple { + constexpr int A = __builtin_bit_cast(int, 10); + static_assert(A == 10); + + static_assert(__builtin_bit_cast(unsigned, 1.0F) == 1065353216); + + struct Bytes { + char a, b, c, d; + }; + constexpr unsigned B = __builtin_bit_cast(unsigned, Bytes{10, 12, 13, 14}); + static_assert(B == (LITTLE_END ? 235736074 : 168561934)); + + + constexpr unsigned C = __builtin_bit_cast(unsigned, (_BitInt(32))12); + static_assert(C == 12); + + struct BitInts { + _BitInt(16) a; + _BitInt(16) b; + }; + constexpr unsigned D = __builtin_bit_cast(unsigned, BitInts{12, 13}); + static_assert(D == (LITTLE_END ? 851980 : 786445)); + + + + static_assert(__builtin_bit_cast(char, true) == 1); + + 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)); + // static_assert(round_trip((int)0x0C05FEFE)); + + + /// This works in GCC and in the bytecode interpreter, but the current interpreter + /// diagnoses it. + static_assert(__builtin_bit_cast(intptr_t, nullptr) == 0); // ref-error {{not an integral constant expression}} \ + // ref-note {{indeterminate value can only initialize an object}} +} + +namespace bitint { + constexpr _BitInt(sizeof(int) * 8) BI = ~0; + constexpr unsigned int I = __builtin_bit_cast(unsigned int, BI); + static_assert(I == ~0u, ""); + + constexpr _BitInt(sizeof(int) * 8) IB = __builtin_bit_cast(_BitInt(sizeof(int) * 8), I); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{constexpr bit cast involving type '_BitInt(32)' is not yet supported}} \ + // ref-note {{declared here}} + static_assert(IB == ~0u, ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'IB' is not a constant expression}} +} + +namespace BitFields { + struct BitFields { + unsigned a : 2; + unsigned b : 30; + }; + + constexpr unsigned A = __builtin_bit_cast(unsigned, BitFields{3, 16}); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{not yet supported}} \ + // ref-note {{declared here}} + static_assert(A == (LITTLE_END ? 67 : 3221225488)); // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'A'}} + + + void bitfield_indeterminate() { + struct BF { unsigned char z : 2; }; + enum byte : unsigned char {}; + + constexpr BF bf = {0x3}; + /// Requires bitcasts to composite types. + // static_assert(bit_cast>(bf).bits == bf.z); + // static_assert(bit_cast(bf)); + +#if 0 + // 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; + } +#endif + } +} + +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); + +#if 0 +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)); +#endif + + + +/// --------------------------------------------------------------------------- +/// From here on, it's things copied from test/SemaCXX/constexpr-builtin-bit.cast.cpp + +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)); +} + +void test_array() { + constexpr unsigned char input[] = {0xCA, 0xFE, 0xBA, 0xBE}; + constexpr unsigned expected = LITTLE_END ? 0xBEBAFECA : 0xCAFEBABE; + static_assert(bit_cast(input) == expected); + + /// Same things but with a composite array. + struct US { unsigned char I; }; + constexpr US input2[] = {{0xCA}, {0xFE}, {0xBA}, {0xBE}}; + static_assert(bit_cast(input2) == expected); +} + +void test_record() { + struct int_splicer { + unsigned x; + unsigned 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)); + + /// FIXME: Bit casts to composite types. + // static_assert(bit_cast(0xCAFEBABE0C05FEFE).x == (LITTLE_END + // ? 0x0C05FEFE + // : 0xCAFEBABE)); + + // static_assert(check_round_trip(splice)); + // static_assert(check_round_trip(splice)); + + struct base2 { + }; + + struct base3 { + unsigned z; + }; + + struct bases : int_splicer, base2, base3 { + unsigned doublez; + }; + + struct tuple4 { + unsigned x, y, z, doublez; + + bool operator==(tuple4 const &other) const = default; + constexpr bool operator==(bases 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); + // static_assert(t4 == tuple4{1, 2, 3, 4}); + // static_assert(round_trip(b)); + + // constexpr auto b2 = bit_cast(t4); + // static_assert(t4 == b2); +} + +void 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)); + +#if 0 + constexpr pad pir{4, 4}; + constexpr int piw = bit_cast(pir).x; // both-error {{constexpr variable 'piw' must be initialized by a constant expression}} \ + // both-note {{in call to 'bit_cast(pir)'}} + + + constexpr no_pad bad = bit_cast(pir); // both-error {{constexpr variable 'bad' must be initialized by a constant expression}} \ + // both-note {{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); +#endif +} + + +void bad_types() { +#if 0 + union X { + int x; + }; + + struct G { + int g; + }; + // expected-error@+2 {{constexpr variable 'g' must be initialized by a constant expression}} + // expected-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@+2 {{constexpr variable 'x' must be initialized by a constant expression}} + // expected-note@+1 {{bit_cast to a union type is not allowed in a constant expression}} + constexpr X x = __builtin_bit_cast(X, G{0}); +#endif + struct has_pointer { + int *ptr; // both-note {{invalid type 'int *' is a member of 'has_pointer'}} + }; + + constexpr intptr_t ptr = __builtin_bit_cast(intptr_t, has_pointer{0}); // both-error {{constexpr variable 'ptr' must be initialized by a constant expression}} \ + // both-note {{bit_cast from a pointer type is not allowed in a constant expression}} + +#if 0 + // expected-error@+2 {{constexpr variable 'hptr' must be initialized by a constant expression}} + // expected-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); +#endif +} + +void test_array_fill() { + constexpr unsigned char a[4] = {1, 2}; + constexpr unsigned int i = bit_cast(a); + static_assert(i == (LITTLE_END ? 0x00000201 : 0x01020000)); +}