From 047249032d70e486380e8790915a8edeb70add56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Mon, 27 Nov 2023 12:10:48 +0100 Subject: [PATCH 01/14] [clang][Interp][NFC] Remove unused include --- clang/lib/AST/Interp/ByteCodeExprGen.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp index f45e8624a7741..f7f8e6c73d84e 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -15,7 +15,6 @@ #include "Function.h" #include "PrimType.h" #include "Program.h" -#include "State.h" using namespace clang; using namespace clang::interp; From 0c62a9021aa293c128c2dd2ef99e78aa813016e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Tue, 11 Jul 2023 13:45:04 +0200 Subject: [PATCH 02/14] Bitcasts --- clang/lib/AST/CMakeLists.txt | 1 + clang/lib/AST/Interp/Boolean.h | 15 +- clang/lib/AST/Interp/ByteCodeExprGen.cpp | 71 +++ clang/lib/AST/Interp/ByteCodeExprGen.h | 1 + clang/lib/AST/Interp/Floating.h | 11 + clang/lib/AST/Interp/Integral.h | 19 +- clang/lib/AST/Interp/Interp.cpp | 17 + clang/lib/AST/Interp/Interp.h | 66 ++ clang/lib/AST/Interp/InterpBitcast.cpp | 482 +++++++++++++++ clang/lib/AST/Interp/Opcodes.td | 17 + clang/lib/AST/Interp/PrimType.h | 4 + clang/test/AST/Interp/builtin-bit-cast.cpp | 683 +++++++++++++++++++++ clang/test/AST/Interp/literals.cpp | 5 + 13 files changed, 1389 insertions(+), 3 deletions(-) create mode 100644 clang/lib/AST/Interp/InterpBitcast.cpp create mode 100644 clang/test/AST/Interp/builtin-bit-cast.cpp diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index fe3f8c485ec1c..5a188b583022b 100644 --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -74,6 +74,7 @@ add_clang_library(clangAST Interp/Frame.cpp Interp/Function.cpp Interp/InterpBuiltin.cpp + Interp/InterpBitcast.cpp Interp/Floating.cpp Interp/Interp.cpp Interp/InterpBlock.cpp diff --git a/clang/lib/AST/Interp/Boolean.h b/clang/lib/AST/Interp/Boolean.h index 336f7941dfc47..6531c5ac6945b 100644 --- a/clang/lib/AST/Interp/Boolean.h +++ b/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 { @@ -66,6 +67,9 @@ class Boolean final { 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(); } @@ -107,6 +111,13 @@ class Boolean final { 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 diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp index f7f8e6c73d84e..efe79c84c1151 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -72,6 +72,74 @@ template class OptionScope final { } // 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(*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. + 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) { const Expr *SubExpr = CE->getSubExpr(); @@ -92,6 +160,9 @@ bool ByteCodeExprGen::VisitCastExpr(const CastExpr *CE) { }); } + case CK_LValueToRValueBitCast: + return this->emitBuiltinBitCast(CE); + case CK_UncheckedDerivedToBase: case CK_DerivedToBase: { if (!this->visit(SubExpr)) diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.h b/clang/lib/AST/Interp/ByteCodeExprGen.h index bc1d5d11a1151..cbb1b5d1aa04c 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.h +++ b/clang/lib/AST/Interp/ByteCodeExprGen.h @@ -288,6 +288,7 @@ class ByteCodeExprGen : public ConstStmtVisitor, bool>, bool emitRecordDestruction(const Descriptor *Desc); unsigned collectBaseOffset(const RecordType *BaseType, const RecordType *DerivedType); + bool emitBuiltinBitCast(const CastExpr *E); protected: /// Variable to storage mapping. diff --git a/clang/lib/AST/Interp/Floating.h b/clang/lib/AST/Interp/Floating.h index e4ac76d8509fb..130c4ae402a34 100644 --- a/clang/lib/AST/Interp/Floating.h +++ b/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 { @@ -84,6 +85,12 @@ class Floating final { } 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(); } @@ -134,6 +141,10 @@ 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 { diff --git a/clang/lib/AST/Interp/Integral.h b/clang/lib/AST/Interp/Integral.h index cc1cab8f39fb1..2e47c2eaeeaa7 100644 --- a/clang/lib/AST/Interp/Integral.h +++ b/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" @@ -119,6 +120,10 @@ template class Integral final { } 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; } @@ -185,6 +190,18 @@ template class Integral final { 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); } diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp index 13b77e9a87725..7fa121fc823da 100644 --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -607,7 +607,24 @@ bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR) { return false; } } + return false; +} + +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; } diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h index 4f7778bdd2ff3..4bf2c83baa2cc 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -187,6 +187,9 @@ bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result, /// Checks why the given DeclRefExpr is invalid. bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR); +bool CheckBitcast(InterpState &S, CodePtr OpPC, unsigned IndeterminateBits, + bool TargetIsUCharOrByte); + /// Interpreter entry point. bool Interpret(InterpState &S, APValue &Result); @@ -198,6 +201,18 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F, bool InterpretOffsetOf(InterpState &S, CodePtr OpPC, const OffsetOfExpr *E, llvm::ArrayRef ArrayIndices, int64_t &Result); +/// 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 }; //===----------------------------------------------------------------------===// @@ -1561,6 +1576,57 @@ template bool Cast(InterpState &S, CodePtr OpPC) { 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, diff --git a/clang/lib/AST/Interp/InterpBitcast.cpp b/clang/lib/AST/Interp/InterpBitcast.cpp new file mode 100644 index 0000000000000..91326fc8e7880 --- /dev/null +++ b/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(); + return true; + } + if (T == PT_Ptr) { + assert(P.getType()->isNullPtrType()); + // Just need to write out a nullptr. + P.deref() = Pointer(); + P.initialize(); + 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(); + }); + return true; + }); + + return Success; +} +} // namespace interp +} // namespace clang diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td index 69068e87d5720..2c1e39621d79f 100644 --- a/clang/lib/AST/Interp/Opcodes.td +++ b/clang/lib/AST/Interp/Opcodes.td @@ -585,6 +585,23 @@ def Cast: Opcode { 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]; diff --git a/clang/lib/AST/Interp/PrimType.h b/clang/lib/AST/Interp/PrimType.h index 8c5e87f37be18..66f4b747a13db 100644 --- a/clang/lib/AST/Interp/PrimType.h +++ b/clang/lib/AST/Interp/PrimType.h @@ -87,6 +87,10 @@ template <> struct PrimConv { /// 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 *); diff --git a/clang/test/AST/Interp/builtin-bit-cast.cpp b/clang/test/AST/Interp/builtin-bit-cast.cpp new file mode 100644 index 0000000000000..c5b8ca12570a1 --- /dev/null +++ b/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}} + + // 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}} + + 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}} +}; diff --git a/clang/test/AST/Interp/literals.cpp b/clang/test/AST/Interp/literals.cpp index 85adfe551384d..6a881b0c1563a 100644 --- a/clang/test/AST/Interp/literals.cpp +++ b/clang/test/AST/Interp/literals.cpp @@ -148,6 +148,11 @@ constexpr const int *p = &m; 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; } From 4feef2c3df74ed3c1adeb4abb5eb8fba0bff1a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Thu, 5 Oct 2023 11:45:09 +0200 Subject: [PATCH 03/14] Implement bitfield bit casts to prim types --- clang/lib/AST/Interp/InterpBitcast.cpp | 40 ++++++++++++++-------- clang/lib/AST/Interp/Record.h | 3 ++ clang/test/AST/Interp/builtin-bit-cast.cpp | 25 ++++++++++++++ 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/clang/lib/AST/Interp/InterpBitcast.cpp b/clang/lib/AST/Interp/InterpBitcast.cpp index 91326fc8e7880..5d5b5d549d566 100644 --- a/clang/lib/AST/Interp/InterpBitcast.cpp +++ b/clang/lib/AST/Interp/InterpBitcast.cpp @@ -136,7 +136,7 @@ struct BitcastBuffer { 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) { + std::byte *getBytes(size_t ByteOffset, size_t NBytes, size_t NBits) { assert(ByteOffset >= this->ByteOffset && "we don't support stepping back"); // All untouched bits before the requested bit offset @@ -144,14 +144,15 @@ struct BitcastBuffer { // because they can't be read into non-uchar/non-std::byte // values. IndeterminateBits += (ByteOffset - this->ByteOffset); + IndeterminateBits += ((NBytes * 8) - NBits); size_t OldOffset = this->Offset; - this->Offset += N; - this->ByteOffset = ByteOffset + N; + this->Offset += NBytes; + this->ByteOffset = ByteOffset + NBytes; if (BigEndian) - return Buff + BuffSize - OldOffset - N; + return Buff + BuffSize - OldOffset - NBytes; // Little Endian target. return Buff + OldOffset; @@ -213,7 +214,8 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset, // TODO: Virtual bases? for (unsigned I = 0; I != R->getNumFields(); ++I) { - Pointer Elem = P.atField(R->getField(I)->Offset); + const Record::Field *Fi = R->getField(I); + Pointer Elem = P.atField(Fi->Offset); CharUnits FieldOffset = Ctx.getASTContext().toCharUnitsFromBits(Layout.getFieldOffset(I)); size_t ByteOffset = Offset + FieldOffset.getQuantity(); @@ -302,6 +304,7 @@ static bool CheckBitcastType(InterpState &S, CodePtr OpPC, QualType T, /// 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) { + llvm::errs() << __PRETTY_FUNCTION__ << "\n"; assert(P.isLive()); assert(Buff); assert(BuffSize > 0); @@ -313,11 +316,10 @@ bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff, const Context &Ctx = S.getContext(); const ASTContext &ASTCtx = Ctx.getASTContext(); - uint64_t PointerSize = - ASTCtx - .toCharUnitsFromBits( - ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default)) - .getQuantity(); + uint64_t PointerSizeBits = + ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default); + uint64_t PointerSizeBytes = + ASTCtx.toCharUnitsFromBits(PointerSizeBits).getQuantity(); bool Success = enumeratePointerFields( P, S.getContext(), @@ -326,16 +328,24 @@ bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff, return false; if (T == PT_Ptr) { assert(Ptr.getType()->isNullPtrType()); - std::byte *M = F.getBytes(ByteOffset, PointerSize); - std::memset(M, 0, PointerSize); + std::byte *M = + F.getBytes(ByteOffset, PointerSizeBytes, PointerSizeBits); + std::memset(M, 0, PointerSizeBytes); return true; } + CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(Ptr.getType()); + unsigned ObjectReprBytes = + ASTCtx.getTypeSizeInChars(Ptr.getType()).getQuantity(); + unsigned BitWidth; + if (const FieldDecl *FD = Ptr.getField(); FD && FD->isBitField()) + BitWidth = FD->getBitWidthValue(ASTCtx); + else + BitWidth = ASTCtx.toBits(ObjectReprChars); + BITCAST_TYPE_SWITCH_WITH_FLOAT(T, { T Val = Ptr.deref(); - unsigned ObjectReprBytes = - ASTCtx.getTypeSizeInChars(Ptr.getType()).getQuantity(); - std::byte *M = F.getBytes(ByteOffset, ObjectReprBytes); + std::byte *M = F.getBytes(ByteOffset, ObjectReprBytes, BitWidth); Val.bitcastToMemory(M); }); return true; diff --git a/clang/lib/AST/Interp/Record.h b/clang/lib/AST/Interp/Record.h index b0952af2d1ac6..dbaa0eb3fed70 100644 --- a/clang/lib/AST/Interp/Record.h +++ b/clang/lib/AST/Interp/Record.h @@ -30,6 +30,9 @@ class Record final { unsigned Offset; Descriptor *Desc; bool isBitField() const { return Decl->isBitField(); } + unsigned getBitWidth(const ASTContext &C) const { + return Decl->getBitWidthValue(C); + } }; /// Describes a base class. diff --git a/clang/test/AST/Interp/builtin-bit-cast.cpp b/clang/test/AST/Interp/builtin-bit-cast.cpp index c5b8ca12570a1..f47591427c900 100644 --- a/clang/test/AST/Interp/builtin-bit-cast.cpp +++ b/clang/test/AST/Interp/builtin-bit-cast.cpp @@ -681,3 +681,28 @@ namespace StringLiterals { static_assert(Foo.data[3] == '\0', ""); // ref-error {{not an integral constant expression}} \ // ref-note {{initializer of 'Foo' is not a constant expression}} }; + +/// The current interpreter does not support bitcasts involving bitfields at all, +/// so the following is mainly from comparing diagnostic output with GCC. +namespace Bitfields { + struct S { + char a : 8; + }; + + constexpr S s{4}; + constexpr char c = __builtin_bit_cast(char, s); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{bit_cast involving bit-field is not yet supported}} \ + // ref-note{{declared here}} + static_assert(c == 4, ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'c' is not a constant expression}} + + + struct S2 { + char a : 4; + }; + constexpr S2 s2{4}; + constexpr char c2 = __builtin_bit_cast(char, s2); // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'char' is invalid}} \ + // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{bit_cast involving bit-field is not yet supported}} +} From 7006c2e8534e1db0817723fe2f87385e87cc2a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Tue, 17 Oct 2023 16:19:04 +0200 Subject: [PATCH 04/14] More bitfield work --- clang/lib/AST/Interp/InterpBitcast.cpp | 124 ++++++++++++--------- clang/test/AST/Interp/builtin-bit-cast.cpp | 48 ++++++++ 2 files changed, 118 insertions(+), 54 deletions(-) diff --git a/clang/lib/AST/Interp/InterpBitcast.cpp b/clang/lib/AST/Interp/InterpBitcast.cpp index 5d5b5d549d566..60d9eb544a846 100644 --- a/clang/lib/AST/Interp/InterpBitcast.cpp +++ b/clang/lib/AST/Interp/InterpBitcast.cpp @@ -12,6 +12,7 @@ #include "clang/AST/RecordLayout.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/TargetInfo.h" +#include "llvm/ADT/BitVector.h" namespace clang { namespace interp { @@ -20,7 +21,7 @@ namespace interp { /// Used to iterate over pointer fields. using DataFunc = - llvm::function_ref; + llvm::function_ref; #define BITCAST_TYPE_SWITCH(Expr, B) \ do { \ @@ -65,14 +66,14 @@ static void swapBytes(std::byte *M, size_t N) { std::swap(M[I], M[N - 1 - I]); } -/// Track what bytes have been initialized to known values and which ones +/// Track what bits have been initialized to known values and which ones /// have indeterminate value. -/// All offsets are in bytes. -struct ByteTracker { - std::vector Initialized; +/// All offsets are in bits. +struct BitTracker { + llvm::BitVector Initialized; std::vector Data; - ByteTracker() = default; + BitTracker() = default; size_t size() const { assert(Initialized.size() == Data.size()); @@ -126,36 +127,53 @@ struct ByteTracker { }; struct BitcastBuffer { + llvm::BitVector Data; std::byte *Buff; - size_t ByteOffset = 0; - size_t Offset = 0; + size_t BitOffset = 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 NBytes, size_t NBits) { - assert(ByteOffset >= this->ByteOffset && "we don't support stepping back"); + BitcastBuffer(std::byte *Buff, size_t BuffSize) + : Buff(Buff), BuffSize(BuffSize) {} + + void pushData(const std::byte *data, size_t BitOffset, size_t BitWidth) { + assert(BitOffset >= Data.size()); + // First, fill up the bit vector until BitOffset. The bits are all 0 + // but we record them as indeterminate. + { + size_t FillBits = BitOffset - Data.size(); + IndeterminateBits += FillBits; + Data.resize(BitOffset, false); + } - // 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); - IndeterminateBits += ((NBytes * 8) - NBits); + size_t BitsHandled = 0; + // Read all full bytes first + for (size_t I = 0; I != BitWidth / 8; ++I) { + for (unsigned X = 0; X != 8; ++X) { + Data.push_back((data[I] & std::byte(1 << X)) != std::byte{0}); + ++BitsHandled; + } + } - size_t OldOffset = this->Offset; + // Rest of the bits. + assert((BitWidth - BitsHandled) < 8); + for (size_t I = 0, E = (BitWidth - BitsHandled); I != E; ++I) { + Data.push_back((data[BitWidth / 8] & std::byte(1 << I)) != std::byte{0}); + ++BitsHandled; + } + } - this->Offset += NBytes; - this->ByteOffset = ByteOffset + NBytes; + void pushZeroes(size_t Amount) { Data.resize(Data.size() + Amount, false); } - if (BigEndian) - return Buff + BuffSize - OldOffset - NBytes; + void finish() { + // Fill up with zeroes until the buffer is BuffSize in size. + // The added bits are of indeterminate value. + assert(Data.size() <= (BuffSize * 8)); + size_t Remainder = (BuffSize * 8) - Data.size(); + for (size_t I = 0; I != Remainder; ++I) + Data.push_back(false); - // Little Endian target. - return Buff + OldOffset; + IndeterminateBits += Remainder; } }; @@ -174,13 +192,12 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset, if (FieldDesc->isPrimitiveArray()) { QualType ElemType = FieldDesc->getType()->getAsArrayTypeUnsafe()->getElementType(); - size_t ElemSize = - Ctx.getASTContext().getTypeSizeInChars(ElemType).getQuantity(); + size_t ElemSizeInBits = Ctx.getASTContext().getTypeSize(ElemType); PrimType ElemT = *Ctx.classify(ElemType); for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) { if (!F(P.atIndex(I), ElemT, Offset)) return false; - Offset += ElemSize; + Offset += ElemSizeInBits; } return true; } @@ -189,11 +206,10 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset, if (FieldDesc->isCompositeArray()) { QualType ElemType = FieldDesc->getType()->getAsArrayTypeUnsafe()->getElementType(); - size_t ElemSize = - Ctx.getASTContext().getTypeSizeInChars(ElemType).getQuantity(); + size_t ElemSizeInBits = Ctx.getASTContext().getTypeSize(ElemType); for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) { enumerateData(P.atIndex(I).narrow(), Ctx, Offset, F); - Offset += ElemSize; + Offset += ElemSizeInBits; } return true; } @@ -205,10 +221,10 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset, 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)) + CharUnits ByteOffset = + Layout.getBaseClassOffset(cast(B.Decl)); + size_t BitOffset = Offset + Ctx.getASTContext().toBits(ByteOffset); + if (!enumerateData(Elem, Ctx, BitOffset, F)) return false; } // TODO: Virtual bases? @@ -216,10 +232,8 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset, for (unsigned I = 0; I != R->getNumFields(); ++I) { const Record::Field *Fi = R->getField(I); Pointer Elem = P.atField(Fi->Offset); - CharUnits FieldOffset = - Ctx.getASTContext().toCharUnitsFromBits(Layout.getFieldOffset(I)); - size_t ByteOffset = Offset + FieldOffset.getQuantity(); - if (!enumerateData(Elem, Ctx, ByteOffset, F)) + size_t BitOffset = Offset + Layout.getFieldOffset(I); + if (!enumerateData(Elem, Ctx, BitOffset, F)) return false; } return true; @@ -309,34 +323,28 @@ bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff, assert(Buff); assert(BuffSize > 0); - BitcastBuffer F(Buff, BuffSize, S.getCtx().getTargetInfo().isBigEndian()); + BitcastBuffer F(Buff, BuffSize); if (!CheckBitcastType(S, OpPC, P.getType(), /*IsToType=*/false)) return false; const Context &Ctx = S.getContext(); const ASTContext &ASTCtx = Ctx.getASTContext(); - uint64_t PointerSizeBits = + uint64_t PointerSizeInBits = ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default); - uint64_t PointerSizeBytes = - ASTCtx.toCharUnitsFromBits(PointerSizeBits).getQuantity(); bool Success = enumeratePointerFields( P, S.getContext(), - [&](const Pointer &Ptr, PrimType T, size_t ByteOffset) -> bool { + [&](const Pointer &Ptr, PrimType T, size_t BitOffset) -> bool { if (!Ptr.isInitialized()) return false; if (T == PT_Ptr) { assert(Ptr.getType()->isNullPtrType()); - std::byte *M = - F.getBytes(ByteOffset, PointerSizeBytes, PointerSizeBits); - std::memset(M, 0, PointerSizeBytes); + F.pushZeroes(PointerSizeInBits); return true; } CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(Ptr.getType()); - unsigned ObjectReprBytes = - ASTCtx.getTypeSizeInChars(Ptr.getType()).getQuantity(); unsigned BitWidth; if (const FieldDecl *FD = Ptr.getField(); FD && FD->isBitField()) BitWidth = FD->getBitWidthValue(ASTCtx); @@ -345,13 +353,19 @@ bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff, BITCAST_TYPE_SWITCH_WITH_FLOAT(T, { T Val = Ptr.deref(); - std::byte *M = F.getBytes(ByteOffset, ObjectReprBytes, BitWidth); - Val.bitcastToMemory(M); + std::byte Buff[sizeof(T)]; + Val.bitcastToMemory(Buff); + F.pushData(Buff, BitOffset, BitWidth); }); return true; }); + F.finish(); + IndeterminateBits = F.IndeterminateBits; + assert(F.Data.size() == BuffSize * 8); + std::memcpy(Buff, F.Data.getData().data(), BuffSize); + return Success; } @@ -385,10 +399,11 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, .getQuantity(); bool BigEndian = ASTCtx.getTargetInfo().isBigEndian(); - ByteTracker Bytes; + BitTracker Bytes; enumeratePointerFields( P, S.getContext(), [&](const Pointer &P, PrimType T, size_t ByteOffset) -> bool { + ByteOffset /= 8; bool PtrInitialized = P.isInitialized(); if (!PtrInitialized) { Bytes.markUninitializedUntil(ByteOffset + primSize(T)); @@ -425,6 +440,7 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, bool Success = enumeratePointerFields( DestPtr, S.getContext(), [&](const Pointer &P, PrimType T, size_t ByteOffset) -> bool { + ByteOffset /= 8; if (T == PT_Float) { const QualType FloatType = P.getFieldDesc()->getType(); const auto &Sem = ASTCtx.getFloatTypeSemantics(FloatType); diff --git a/clang/test/AST/Interp/builtin-bit-cast.cpp b/clang/test/AST/Interp/builtin-bit-cast.cpp index f47591427c900..4aa17501a30e3 100644 --- a/clang/test/AST/Interp/builtin-bit-cast.cpp +++ b/clang/test/AST/Interp/builtin-bit-cast.cpp @@ -22,6 +22,9 @@ typedef decltype(nullptr) nullptr_t; + +#if 0 + static_assert(sizeof(int) == 4); static_assert(sizeof(long long) == 8); @@ -682,6 +685,7 @@ namespace StringLiterals { // ref-note {{initializer of 'Foo' is not a constant expression}} }; +#endif /// The current interpreter does not support bitcasts involving bitfields at all, /// so the following is mainly from comparing diagnostic output with GCC. namespace Bitfields { @@ -705,4 +709,48 @@ namespace Bitfields { // expected-note {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'char' is invalid}} \ // ref-error {{must be initialized by a constant expression}} \ // ref-note {{bit_cast involving bit-field is not yet supported}} + + struct A { + unsigned char a : 4; + unsigned char b : 4; + }; + + constexpr A b{12, 3}; + static_assert(b.a == 12, ""); + static_assert(b.b == 3, ""); + constexpr unsigned char a = __builtin_bit_cast(unsigned char, b); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{bit_cast involving bit-field is not yet supported}} \ + // ref-note {{declared here}} + static_assert(a == 60, ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'a' is not a constant expression}} + + struct Byte { + unsigned char a : 1; + unsigned char b : 1; + unsigned char c : 1; + unsigned char d : 1; + unsigned char e : 1; + unsigned char f : 1; + unsigned char g : 1; + unsigned char h : 1; + }; + + constexpr Byte B = {1, 1, 0, 1, 1, 0, 0, 1}; + constexpr char C = __builtin_bit_cast(char, B); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{bit_cast involving bit-field is not yet supported}} \ + // ref-note {{declared here}} + + static_assert(C == -101); // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'C' is not a constant expression}} + + struct P { + unsigned short s1 : 5; + short s2; + }; + + constexpr P p = {24, -10}; + constexpr int I = __builtin_bit_cast(int, p); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{bit_cast involving bit-field is not yet supported}} \ + // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{indeterminate value}} } From 1591e2cc7b69a7036c8ee25cd6ff58024cb3597a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Tue, 17 Oct 2023 18:57:58 +0200 Subject: [PATCH 05/14] All existing tests work. --- clang/lib/AST/Interp/Boolean.h | 3 + clang/lib/AST/Interp/Integral.h | 3 + clang/lib/AST/Interp/InterpBitcast.cpp | 179 +++++++++++++++------ clang/test/AST/Interp/builtin-bit-cast.cpp | 17 +- 4 files changed, 147 insertions(+), 55 deletions(-) diff --git a/clang/lib/AST/Interp/Boolean.h b/clang/lib/AST/Interp/Boolean.h index 6531c5ac6945b..0708294dab95a 100644 --- a/clang/lib/AST/Interp/Boolean.h +++ b/clang/lib/AST/Interp/Boolean.h @@ -69,6 +69,9 @@ class Boolean final { constexpr static unsigned bitWidth() { return 1; } constexpr static unsigned objectReprBits() { return 8; } constexpr static unsigned valueReprBytes(const ASTContext &Ctx) { return 1; } + constexpr static unsigned valueReprBits(const ASTContext &Ctx) { + return 8; + } // FIXME: Is this correct? bool isZero() const { return !V; } bool isMin() const { return isZero(); } diff --git a/clang/lib/AST/Interp/Integral.h b/clang/lib/AST/Interp/Integral.h index 2e47c2eaeeaa7..bcef75bbbaeb8 100644 --- a/clang/lib/AST/Interp/Integral.h +++ b/clang/lib/AST/Interp/Integral.h @@ -121,6 +121,9 @@ template class Integral final { constexpr static unsigned bitWidth() { return Bits; } constexpr static unsigned objectReprBits() { return Bits; } + constexpr static unsigned valueReprBits(const ASTContext &Ctx) { + return Bits; + } constexpr static unsigned valueReprBytes(const ASTContext &Ctx) { return Ctx.toCharUnitsFromBits(Bits).getQuantity(); } diff --git a/clang/lib/AST/Interp/InterpBitcast.cpp b/clang/lib/AST/Interp/InterpBitcast.cpp index 60d9eb544a846..b9922f855d460 100644 --- a/clang/lib/AST/Interp/InterpBitcast.cpp +++ b/clang/lib/AST/Interp/InterpBitcast.cpp @@ -71,25 +71,68 @@ static void swapBytes(std::byte *M, size_t N) { /// All offsets are in bits. struct BitTracker { llvm::BitVector Initialized; + llvm::BitVector Data_; std::vector Data; BitTracker() = default; size_t size() const { assert(Initialized.size() == Data.size()); + assert(Initialized.size() == Data_.size()); return Initialized.size(); } - std::byte *getBytes(size_t Offset) { return Data.data() + Offset; } + std::byte *getBytes(size_t Offset) { + assert(false); + return Data.data() + Offset; + } + + const std::byte *getBytes(size_t BitOffset, int a) { + assert(BitOffset % 8 == 0); + return reinterpret_cast(Data_.getData().data()) + + (BitOffset / 8); + } + bool allInitialized(size_t Offset, size_t Size) const { - for (size_t I = Offset; I != (Size + Offset); ++I) { - if (!Initialized[I]) - return false; + return Initialized.find_first_unset_in(Offset, Offset + Size) == -1; + } + + void pushData(const std::byte *data, size_t BitOffset, size_t BitWidth) { + assert(BitOffset >= Data_.size()); + // First, fill up the bit vector until BitOffset. The bits are all 0 + // but we record them as indeterminate. + { + Data_.resize(BitOffset, false); + Initialized.resize(BitOffset, false); } - return true; + + size_t BitsHandled = 0; + // Read all full bytes first + for (size_t I = 0; I != BitWidth / 8; ++I) { + for (unsigned X = 0; X != 8; ++X) { + Data_.push_back((data[I] & std::byte(1 << X)) != std::byte{0}); + Initialized.push_back(true); + ++BitsHandled; + } + } + + // Rest of the bits. + assert((BitWidth - BitsHandled) < 8); + for (size_t I = 0, E = (BitWidth - BitsHandled); I != E; ++I) { + Data_.push_back((data[BitWidth / 8] & std::byte(1 << I)) != std::byte{0}); + Initialized.push_back(true); + ++BitsHandled; + } + } + + void pushZeroes(size_t Amount) { + Data_.resize(Data_.size() + Amount, false); + Initialized.resize(Data_.size() + Amount, true); } +#if 0 std::byte *getWritableBytes(size_t Offset, size_t Size, bool InitValue) { + assert(false); assert(Offset >= Data.size()); assert(Size > 0); @@ -104,17 +147,17 @@ struct BitTracker { return Data.data() + Offset; } +#endif 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); + assert(Offset >= Data_.size()); + Data_.resize(Offset, false); + Initialized.resize(Offset, false); + return; } void zeroUntil(size_t Offset) { + assert(false); assert(Offset >= Data.size()); assert(Data.size() == Initialized.size()); @@ -330,6 +373,7 @@ bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff, const Context &Ctx = S.getContext(); const ASTContext &ASTCtx = Ctx.getASTContext(); + bool BigEndian = ASTCtx.getTargetInfo().isBigEndian(); uint64_t PointerSizeInBits = ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default); @@ -353,9 +397,13 @@ bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff, BITCAST_TYPE_SWITCH_WITH_FLOAT(T, { T Val = Ptr.deref(); - std::byte Buff[sizeof(T)]; + std::byte *Buff = (std::byte *)std::malloc( + ObjectReprChars.getQuantity()); //[sizeof(T)]; Val.bitcastToMemory(Buff); + if (BigEndian) + swapBytes(Buff, ObjectReprChars.getQuantity()); F.pushData(Buff, BitOffset, BitWidth); + std::free(Buff); }); return true; }); @@ -366,6 +414,8 @@ bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff, assert(F.Data.size() == BuffSize * 8); std::memcpy(Buff, F.Data.getData().data(), BuffSize); + if (BigEndian) + swapBytes(Buff, BuffSize); return Success; } @@ -392,68 +442,87 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, const Context &Ctx = S.getContext(); const ASTContext &ASTCtx = Ctx.getASTContext(); - uint64_t PointerSize = - ASTCtx - .toCharUnitsFromBits( - ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default)) - .getQuantity(); + uint64_t PointerSizeInBits = + ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default); bool BigEndian = ASTCtx.getTargetInfo().isBigEndian(); BitTracker Bytes; enumeratePointerFields( P, S.getContext(), - [&](const Pointer &P, PrimType T, size_t ByteOffset) -> bool { - ByteOffset /= 8; + [&](const Pointer &P, PrimType T, size_t BitOffset) -> bool { bool PtrInitialized = P.isInitialized(); if (!PtrInitialized) { - Bytes.markUninitializedUntil(ByteOffset + primSize(T)); + Bytes.markUninitializedUntil(BitOffset + (primSize(T) * 8)); 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); + Bytes.pushZeroes(PointerSizeInBits); return true; } BITCAST_TYPE_SWITCH_WITH_FLOAT(T, { T Val = P.deref(); unsigned ObjectReprBytes = ASTCtx.getTypeSizeInChars(P.getType()).getQuantity(); + + 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); + unsigned ValueReprBytes = Val.valueReprBytes(ASTCtx); assert(ObjectReprBytes >= ValueReprBytes); - std::byte *Dest = Bytes.getWritableBytes(ByteOffset, ValueReprBytes, - PtrInitialized); - Val.bitcastToMemory(Dest); - Bytes.zeroUntil(ByteOffset + ObjectReprBytes); + std::byte *Buff = (std::byte *)std::malloc( + ObjectReprChars.getQuantity()); //[sizeof(T)]; + // std::byte Buff[sizeof(T)]; + Val.bitcastToMemory(Buff); if (BigEndian) - swapBytes(Dest, ValueReprBytes); + swapBytes(Buff, BitWidth / 8); + // if (BigEndian) XXX + // swapBytes(Dest, ValueReprBytes); + Bytes.pushData(Buff, BitOffset, BitWidth); + std::free(Buff); }); return true; }); bool Success = enumeratePointerFields( DestPtr, S.getContext(), - [&](const Pointer &P, PrimType T, size_t ByteOffset) -> bool { - ByteOffset /= 8; + [&](const Pointer &P, PrimType T, size_t BitOffset) -> 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(); + size_t ValueReprBits = ASTCtx.getTypeSize(FloatType); + + CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(P.getType()); + const std::byte *M = Bytes.getBytes(BitOffset, 1234); + std::byte *Buff = (std::byte *)std::malloc( + ObjectReprChars.getQuantity()); //[sizeof(T)]; + std::memcpy(Buff, M, ObjectReprChars.getQuantity()); + // Val.bitcastToMemory(Buff); + if (BigEndian) + swapBytes(Buff, ObjectReprChars.getQuantity()); - std::byte *M = Bytes.getBytes(ByteOffset); + // F.pushData(Buff, BitOffset, BitWidth); - if (BigEndian) - swapBytes(M, ValueReprBytes); - P.deref() = Floating::bitcastFromMemory(M, Sem); + // const std::byte *M = Bytes.getBytes(BitOffset, 1234); + // size_t ValueReprBytes = + // ASTCtx.toCharUnitsFromBits(APFloat::semanticsSizeInBits(Sem)) + //.getQuantity(); + + // std::byte *M = Bytes.getBytes(ByteOffset); + + // if (BigEndian) + // swapBytes(M, ValueReprBytes); + P.deref() = Floating::bitcastFromMemory(Buff, Sem); P.initialize(); + std::free(Buff); return true; } if (T == PT_Ptr) { @@ -467,19 +536,19 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, BITCAST_TYPE_SWITCH(T, { T &Val = P.deref(); - size_t ValueReprBytes = T::valueReprBytes(ASTCtx); + // size_t ValueReprBytes = T::valueReprBytes(ASTCtx); + size_t ValueReprBits = T::valueReprBits(ASTCtx); // Check if any of the bits we're about to read are uninitialized. - bool HasIndeterminateBytes = - !Bytes.allInitialized(ByteOffset, ValueReprBytes); + bool HasIndeterminateBits = + !Bytes.allInitialized(BitOffset, ValueReprBits); - if (HasIndeterminateBytes) { + if (HasIndeterminateBits) { // 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())); + (P.getType()->isSpecificBuiltinType(BuiltinType::UChar) || + P.getType()->isSpecificBuiltinType(BuiltinType::Char_U) || + P.getType()->isStdByteType()); if (!TargetIsUCharOrBytes) { const Expr *E = S.Current->getExpr(OpPC); @@ -491,12 +560,20 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, } } - std::byte *M = Bytes.getBytes(ByteOffset); - if (BigEndian) - swapBytes(M, ValueReprBytes); - Val = T::bitcastFromMemory(M); + assert(!P.getField()->isBitField()); + + std::byte *Copy = (std::byte *)std::malloc(ValueReprBits / 8); + const std::byte *M = Bytes.getBytes(BitOffset, 1234); + + std::memcpy(Copy, M, ValueReprBits / 8); + + if (BigEndian) { + swapBytes(Copy, ValueReprBits / 8); + } + Val = T::bitcastFromMemory(Copy); + std::free(Copy); - if (!HasIndeterminateBytes) + if (!HasIndeterminateBits) P.initialize(); }); return true; diff --git a/clang/test/AST/Interp/builtin-bit-cast.cpp b/clang/test/AST/Interp/builtin-bit-cast.cpp index 4aa17501a30e3..4f695710877a8 100644 --- a/clang/test/AST/Interp/builtin-bit-cast.cpp +++ b/clang/test/AST/Interp/builtin-bit-cast.cpp @@ -23,7 +23,6 @@ typedef decltype(nullptr) nullptr_t; -#if 0 static_assert(sizeof(int) == 4); static_assert(sizeof(long long) == 8); @@ -409,13 +408,15 @@ namespace MemberPointer { A a = {1, 2}; return bit_cast(a); } + /// FIXME: The following tests need the InitMap changes. +#if 0 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}} - +#endif constexpr A two() { B b = one(); // b.x[1] is indeterminate. @@ -685,7 +686,6 @@ namespace StringLiterals { // ref-note {{initializer of 'Foo' is not a constant expression}} }; -#endif /// The current interpreter does not support bitcasts involving bitfields at all, /// so the following is mainly from comparing diagnostic output with GCC. namespace Bitfields { @@ -753,4 +753,13 @@ namespace Bitfields { // ref-note {{bit_cast involving bit-field is not yet supported}} \ // expected-error {{must be initialized by a constant expression}} \ // expected-note {{indeterminate value}} -} + + + struct CharStruct { + unsigned char v; + }; + constexpr CharStruct CS = __builtin_bit_cast(CharStruct, B); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{bit_cast involving bit-field is not yet supported}} \ + // ref-note {{declared here}} + static_assert(CS.v == 155);} // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'CS' is not a constant expression}} From c5f20a890bb338c51b8e427197db0127e1204839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Wed, 18 Oct 2023 10:53:15 +0200 Subject: [PATCH 06/14] Work on unifying the two implementations --- clang/lib/AST/Interp/Interp.cpp | 4 +- clang/lib/AST/Interp/Interp.h | 18 +- clang/lib/AST/Interp/InterpBitcast.cpp | 261 +++++++------------------ 3 files changed, 79 insertions(+), 204 deletions(-) diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp index 7fa121fc823da..d98d69936fcb6 100644 --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -610,11 +610,11 @@ bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR) { return false; } -bool CheckBitcast(InterpState &S, CodePtr OpPC, unsigned IndeterminateBits, +bool CheckBitcast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits, bool TargetIsUCharOrByte) { // This is always fine. - if (IndeterminateBits == 0) + if (!HasIndeterminateBits) return true; // Indeterminate bits can only be bitcast to unsigned char or std::byte. diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h index 4bf2c83baa2cc..5d69d59053dc5 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -187,7 +187,7 @@ bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result, /// Checks why the given DeclRefExpr is invalid. bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR); -bool CheckBitcast(InterpState &S, CodePtr OpPC, unsigned IndeterminateBits, +bool CheckBitcast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits, bool TargetIsUCharOrByte); /// Interpreter entry point. @@ -205,7 +205,7 @@ bool InterpretOffsetOf(InterpState &S, CodePtr OpPC, const OffsetOfExpr *E, /// 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); + size_t BuffSize, bool &HasIndeterminateBits); /// Perform a bitcast of all fields of P into the fields of DestPtr. /// This performs the actions of a __builtin_bit_cast expression when @@ -1582,12 +1582,12 @@ bool BitCast(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte) { size_t BuffSize = ToT::valueReprBytes(S.getCtx()); std::vector Buff(BuffSize); - unsigned IndeterminateBits = 0; + bool HasIndeterminateBits = false; - if (!DoBitCast(S, OpPC, FromPtr, Buff.data(), BuffSize, IndeterminateBits)) + if (!DoBitCast(S, OpPC, FromPtr, Buff.data(), BuffSize, HasIndeterminateBits)) return false; - if (!CheckBitcast(S, OpPC, IndeterminateBits, TargetIsUCharOrByte)) + if (!CheckBitcast(S, OpPC, HasIndeterminateBits, TargetIsUCharOrByte)) return false; S.Stk.push(ToT::bitcastFromMemory(Buff.data())); @@ -1600,12 +1600,14 @@ inline bool BitCastFP(InterpState &S, CodePtr OpPC, const Pointer &FromPtr = S.Stk.pop(); std::vector Buff(TargetSize); - unsigned IndeterminateBits = 0; + bool HasIndeterminateBits = false; - if (!DoBitCast(S, OpPC, FromPtr, Buff.data(), TargetSize, IndeterminateBits)) + if (!DoBitCast(S, OpPC, FromPtr, Buff.data(), TargetSize, + HasIndeterminateBits)) return false; - if (!CheckBitcast(S, OpPC, IndeterminateBits, /*TargetIsUCharOrByte=*/false)) + if (!CheckBitcast(S, OpPC, HasIndeterminateBits, + /*TargetIsUCharOrByte=*/false)) return false; S.Stk.push(Floating::bitcastFromMemory(Buff.data(), *Sem)); diff --git a/clang/lib/AST/Interp/InterpBitcast.cpp b/clang/lib/AST/Interp/InterpBitcast.cpp index b9922f855d460..b62535c387315 100644 --- a/clang/lib/AST/Interp/InterpBitcast.cpp +++ b/clang/lib/AST/Interp/InterpBitcast.cpp @@ -72,21 +72,14 @@ static void swapBytes(std::byte *M, size_t N) { struct BitTracker { llvm::BitVector Initialized; llvm::BitVector Data_; - std::vector Data; BitTracker() = default; size_t size() const { - assert(Initialized.size() == Data.size()); assert(Initialized.size() == Data_.size()); return Initialized.size(); } - std::byte *getBytes(size_t Offset) { - assert(false); - return Data.data() + Offset; - } - const std::byte *getBytes(size_t BitOffset, int a) { assert(BitOffset % 8 == 0); return reinterpret_cast(Data_.getData().data()) + @@ -97,6 +90,8 @@ struct BitTracker { return Initialized.find_first_unset_in(Offset, Offset + Size) == -1; } + bool allInitialized() const { return Initialized.all(); } + void pushData(const std::byte *data, size_t BitOffset, size_t BitWidth) { assert(BitOffset >= Data_.size()); // First, fill up the bit vector until BitOffset. The bits are all 0 @@ -126,97 +121,15 @@ struct BitTracker { } void pushZeroes(size_t Amount) { - Data_.resize(Data_.size() + Amount, false); - Initialized.resize(Data_.size() + Amount, true); - } - -#if 0 - std::byte *getWritableBytes(size_t Offset, size_t Size, bool InitValue) { - assert(false); - 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; + size_t N = Data_.size(); + Data_.resize(N + Amount, false); + Initialized.resize(N + Amount, true); } -#endif void markUninitializedUntil(size_t Offset) { assert(Offset >= Data_.size()); Data_.resize(Offset, false); Initialized.resize(Offset, false); - return; - } - - void zeroUntil(size_t Offset) { - assert(false); - 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 { - llvm::BitVector Data; - std::byte *Buff; - size_t BitOffset = 0; - size_t BuffSize; - unsigned IndeterminateBits = 0; - - BitcastBuffer(std::byte *Buff, size_t BuffSize) - : Buff(Buff), BuffSize(BuffSize) {} - - void pushData(const std::byte *data, size_t BitOffset, size_t BitWidth) { - assert(BitOffset >= Data.size()); - // First, fill up the bit vector until BitOffset. The bits are all 0 - // but we record them as indeterminate. - { - size_t FillBits = BitOffset - Data.size(); - IndeterminateBits += FillBits; - Data.resize(BitOffset, false); - } - - size_t BitsHandled = 0; - // Read all full bytes first - for (size_t I = 0; I != BitWidth / 8; ++I) { - for (unsigned X = 0; X != 8; ++X) { - Data.push_back((data[I] & std::byte(1 << X)) != std::byte{0}); - ++BitsHandled; - } - } - - // Rest of the bits. - assert((BitWidth - BitsHandled) < 8); - for (size_t I = 0, E = (BitWidth - BitsHandled); I != E; ++I) { - Data.push_back((data[BitWidth / 8] & std::byte(1 << I)) != std::byte{0}); - ++BitsHandled; - } - } - - void pushZeroes(size_t Amount) { Data.resize(Data.size() + Amount, false); } - - void finish() { - // Fill up with zeroes until the buffer is BuffSize in size. - // The added bits are of indeterminate value. - assert(Data.size() <= (BuffSize * 8)); - size_t Remainder = (BuffSize * 8) - Data.size(); - for (size_t I = 0; I != Remainder; ++I) - Data.push_back(false); - - IndeterminateBits += Remainder; } }; @@ -237,12 +150,12 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset, FieldDesc->getType()->getAsArrayTypeUnsafe()->getElementType(); size_t ElemSizeInBits = Ctx.getASTContext().getTypeSize(ElemType); PrimType ElemT = *Ctx.classify(ElemType); + bool Ok = true; for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) { - if (!F(P.atIndex(I), ElemT, Offset)) - return false; + Ok = Ok && F(P.atIndex(I), ElemT, Offset); Offset += ElemSizeInBits; } - return true; + return Ok; } // Composite arrays. @@ -262,13 +175,13 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset, 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); - if (!enumerateData(Elem, Ctx, BitOffset, F)) - return false; + Ok = Ok && enumerateData(Elem, Ctx, BitOffset, F); } // TODO: Virtual bases? @@ -276,10 +189,9 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset, const Record::Field *Fi = R->getField(I); Pointer Elem = P.atField(Fi->Offset); size_t BitOffset = Offset + Layout.getFieldOffset(I); - if (!enumerateData(Elem, Ctx, BitOffset, F)) - return false; + Ok = Ok && enumerateData(Elem, Ctx, BitOffset, F); } - return true; + return Ok; } llvm_unreachable("Unhandled data type"); @@ -357,66 +269,83 @@ static bool CheckBitcastType(InterpState &S, CodePtr OpPC, QualType T, 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) { - llvm::errs() << __PRETTY_FUNCTION__ << "\n"; - assert(P.isLive()); - assert(Buff); - assert(BuffSize > 0); - - BitcastBuffer F(Buff, BuffSize); - - if (!CheckBitcastType(S, OpPC, P.getType(), /*IsToType=*/false)) - return false; - - const Context &Ctx = S.getContext(); +static bool readPointerToBuffer(const Context &Ctx, const Pointer &FromPtr, + BitTracker &Bits, bool RetrnOnUninit) { const ASTContext &ASTCtx = Ctx.getASTContext(); - bool BigEndian = ASTCtx.getTargetInfo().isBigEndian(); uint64_t PointerSizeInBits = ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default); + bool BigEndian = ASTCtx.getTargetInfo().isBigEndian(); - bool Success = enumeratePointerFields( - P, S.getContext(), - [&](const Pointer &Ptr, PrimType T, size_t BitOffset) -> bool { - if (!Ptr.isInitialized()) - return false; + return enumeratePointerFields( + FromPtr, Ctx, + [&](const Pointer &P, PrimType T, size_t BitOffset) -> bool { + if (!P.isInitialized()) { + Bits.markUninitializedUntil(BitOffset + + (primSize(T) * 8)); /// primSize() usage. + return RetrnOnUninit; + } + + 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(Ptr.getType()->isNullPtrType()); - F.pushZeroes(PointerSizeInBits); + assert(P.getType()->isNullPtrType()); + Bits.pushZeroes(PointerSizeInBits); return true; } - CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(Ptr.getType()); + unsigned ObjectReprBytes = + ASTCtx.getTypeSizeInChars(P.getType()).getQuantity(); + + CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(P.getType()); unsigned BitWidth; - if (const FieldDecl *FD = Ptr.getField(); FD && FD->isBitField()) + if (const FieldDecl *FD = P.getField(); FD && FD->isBitField()) BitWidth = FD->getBitWidthValue(ASTCtx); else BitWidth = ASTCtx.toBits(ObjectReprChars); - BITCAST_TYPE_SWITCH_WITH_FLOAT(T, { - T Val = Ptr.deref(); - std::byte *Buff = (std::byte *)std::malloc( - ObjectReprChars.getQuantity()); //[sizeof(T)]; + T Val = P.deref(); + unsigned ValueReprBytes = Val.valueReprBytes(ASTCtx); + assert(ObjectReprBytes >= ValueReprBytes); + + std::byte *Buff = + (std::byte *)std::malloc(ObjectReprChars.getQuantity()); Val.bitcastToMemory(Buff); + if (BigEndian) - swapBytes(Buff, ObjectReprChars.getQuantity()); - F.pushData(Buff, BitOffset, BitWidth); + swapBytes(Buff, BitWidth / 8); + Bits.pushData(Buff, BitOffset, BitWidth); std::free(Buff); }); return true; }); +} - F.finish(); +/// 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, bool &HasIndeterminateBits) { + assert(P.isLive()); + assert(Buff); + assert(BuffSize > 0); - IndeterminateBits = F.IndeterminateBits; - assert(F.Data.size() == BuffSize * 8); - std::memcpy(Buff, F.Data.getData().data(), BuffSize); + if (!CheckBitcastType(S, OpPC, P.getType(), /*IsToType=*/false)) + return false; + + const ASTContext &ASTCtx = S.getContext().getASTContext(); + bool BigEndian = ASTCtx.getTargetInfo().isBigEndian(); + + BitTracker Bits; + bool Success = readPointerToBuffer(S.getContext(), P, Bits, false); + + Bits.markUninitializedUntil(BuffSize * 8); + assert(Bits.size() == BuffSize * 8); + + HasIndeterminateBits = !Bits.allInitialized(); + std::memcpy(Buff, Bits.Data_.getData().data(), BuffSize); if (BigEndian) swapBytes(Buff, BuffSize); - return Success; + return Success; // && !HasIndeterminateBits; } // This function is constexpr if and only if To, From, and the types of @@ -442,55 +371,11 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, const Context &Ctx = S.getContext(); const ASTContext &ASTCtx = Ctx.getASTContext(); - uint64_t PointerSizeInBits = - ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default); bool BigEndian = ASTCtx.getTargetInfo().isBigEndian(); BitTracker Bytes; - enumeratePointerFields( - P, S.getContext(), - [&](const Pointer &P, PrimType T, size_t BitOffset) -> bool { - bool PtrInitialized = P.isInitialized(); - if (!PtrInitialized) { - Bytes.markUninitializedUntil(BitOffset + (primSize(T) * 8)); - 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) { - Bytes.pushZeroes(PointerSizeInBits); - return true; - } - BITCAST_TYPE_SWITCH_WITH_FLOAT(T, { - T Val = P.deref(); - unsigned ObjectReprBytes = - ASTCtx.getTypeSizeInChars(P.getType()).getQuantity(); - - 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); - - unsigned ValueReprBytes = Val.valueReprBytes(ASTCtx); - assert(ObjectReprBytes >= ValueReprBytes); - - std::byte *Buff = (std::byte *)std::malloc( - ObjectReprChars.getQuantity()); //[sizeof(T)]; - // std::byte Buff[sizeof(T)]; - Val.bitcastToMemory(Buff); - - if (BigEndian) - swapBytes(Buff, BitWidth / 8); - // if (BigEndian) XXX - // swapBytes(Dest, ValueReprBytes); - Bytes.pushData(Buff, BitOffset, BitWidth); - std::free(Buff); - }); - return true; - }); + if (!readPointerToBuffer(Ctx, P, Bytes, true)) + return false; bool Success = enumeratePointerFields( DestPtr, S.getContext(), @@ -509,17 +394,6 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, if (BigEndian) swapBytes(Buff, ObjectReprChars.getQuantity()); - // F.pushData(Buff, BitOffset, BitWidth); - - // const std::byte *M = Bytes.getBytes(BitOffset, 1234); - // size_t ValueReprBytes = - // ASTCtx.toCharUnitsFromBits(APFloat::semanticsSizeInBits(Sem)) - //.getQuantity(); - - // std::byte *M = Bytes.getBytes(ByteOffset); - - // if (BigEndian) - // swapBytes(M, ValueReprBytes); P.deref() = Floating::bitcastFromMemory(Buff, Sem); P.initialize(); std::free(Buff); @@ -536,7 +410,6 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, BITCAST_TYPE_SWITCH(T, { T &Val = P.deref(); - // size_t ValueReprBytes = T::valueReprBytes(ASTCtx); size_t ValueReprBits = T::valueReprBits(ASTCtx); // Check if any of the bits we're about to read are uninitialized. bool HasIndeterminateBits = From ee8d32d94cbee7a60787bbfe3fc40eb998092fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Wed, 18 Oct 2023 12:17:53 +0200 Subject: [PATCH 07/14] More bitfield tests --- clang/test/AST/Interp/builtin-bit-cast.cpp | 40 ++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/clang/test/AST/Interp/builtin-bit-cast.cpp b/clang/test/AST/Interp/builtin-bit-cast.cpp index 4f695710877a8..ebc7138e6971f 100644 --- a/clang/test/AST/Interp/builtin-bit-cast.cpp +++ b/clang/test/AST/Interp/builtin-bit-cast.cpp @@ -761,5 +761,41 @@ namespace Bitfields { constexpr CharStruct CS = __builtin_bit_cast(CharStruct, B); // ref-error {{must be initialized by a constant expression}} \ // ref-note {{bit_cast involving bit-field is not yet supported}} \ // ref-note {{declared here}} - static_assert(CS.v == 155);} // ref-error {{not an integral constant expression}} \ - // ref-note {{initializer of 'CS' is not a constant expression}} + static_assert(CS.v == 155); // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'CS' is not a constant expression}} + + + struct I3 { + int a; + int b : 10; + int c; + }; + + struct I32 { + int a; + int b; + int c; + }; + + constexpr I3 i3 {5, 10, 15}; + constexpr I32 i32 = __builtin_bit_cast(I32, i3); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{bit_cast involving bit-field is not yet supported}} \ + // expected-error {{must be initialized by a constant expression}} \ + // expected-note {{indeterminate value can only initialize an object of type 'unsigned char'}} + + struct I33 { + int a; + unsigned char b; + int c; + }; + + constexpr I33 i33 = __builtin_bit_cast(I33, i3); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{bit_cast involving bit-field is not yet supported}} \ + // ref-note 3{{declared here}} + static_assert(i33.a == 5, ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'i33' is not a constant expression}} + static_assert(i33.b == 10, ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'i33' is not a constant expression}} + static_assert(i33.c == 15, ""); // ref-error {{not an integral constant expression}} \ + // ref-note {{initializer of 'i33' is not a constant expression}} +} From 612d3656fafeadb0721207fa05276fdb6b09b902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Wed, 18 Oct 2023 15:46:31 +0200 Subject: [PATCH 08/14] Add string-to-int bitcast test --- clang/test/AST/Interp/builtin-bit-cast.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/clang/test/AST/Interp/builtin-bit-cast.cpp b/clang/test/AST/Interp/builtin-bit-cast.cpp index ebc7138e6971f..554f8e280d1ec 100644 --- a/clang/test/AST/Interp/builtin-bit-cast.cpp +++ b/clang/test/AST/Interp/builtin-bit-cast.cpp @@ -31,9 +31,24 @@ 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}} + // expected-note 2{{indeterminate value can only initialize}} \ + // ref-note {{subexpression not valid}} } + +/// Current interpreter does not support this. +/// https://github.com/llvm/llvm-project/issues/63686 +constexpr int FromString = bit_cast("abc"); // ref-error {{must be initialized by a constant expression}} \ + // ref-note {{in call to}} \ + // ref-note {{declared here}} +#if LITTLE_END +static_assert(FromString == 6513249); // ref-error {{is not an integral constant expression}} \ + // ref-note {{initializer of 'FromString' is not a constant expression}} +#else +static_assert(FromString == 1633837824); // ref-error {{is not an integral constant expression}} \ + // ref-note {{initializer of 'FromString' is not a constant expression}} +#endif + template constexpr bool round_trip(const Init &init) { return bit_cast(bit_cast(init)) == init; From 5c77f917f49d0b130a423471e419e13213a722fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Thu, 9 Nov 2023 12:46:21 +0100 Subject: [PATCH 09/14] Address review comments --- clang/lib/AST/Interp/InterpBitcast.cpp | 3 --- clang/test/AST/Interp/builtin-bit-cast.cpp | 13 +++++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/clang/lib/AST/Interp/InterpBitcast.cpp b/clang/lib/AST/Interp/InterpBitcast.cpp index b62535c387315..ea60aaa87b54d 100644 --- a/clang/lib/AST/Interp/InterpBitcast.cpp +++ b/clang/lib/AST/Interp/InterpBitcast.cpp @@ -17,8 +17,6 @@ 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; @@ -383,7 +381,6 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, if (T == PT_Float) { const QualType FloatType = P.getFieldDesc()->getType(); const auto &Sem = ASTCtx.getFloatTypeSemantics(FloatType); - size_t ValueReprBits = ASTCtx.getTypeSize(FloatType); CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(P.getType()); const std::byte *M = Bytes.getBytes(BitOffset, 1234); diff --git a/clang/test/AST/Interp/builtin-bit-cast.cpp b/clang/test/AST/Interp/builtin-bit-cast.cpp index 554f8e280d1ec..da9115787d75b 100644 --- a/clang/test/AST/Interp/builtin-bit-cast.cpp +++ b/clang/test/AST/Interp/builtin-bit-cast.cpp @@ -49,6 +49,19 @@ static_assert(FromString == 1633837824); // ref-error {{is not an integral const // ref-note {{initializer of 'FromString' is not a constant expression}} #endif + +struct S { + int i, j, k; +}; +constexpr S func() { + constexpr int array[] = { 12, 42, 128 }; + return __builtin_bit_cast(S, array); +} +constexpr S s = func(); +static_assert(s.i == 12, ""); +static_assert(s.j == 42, ""); +static_assert(s.k == 128, ""); + template constexpr bool round_trip(const Init &init) { return bit_cast(bit_cast(init)) == init; From afd8a498fe9ee674d051f793400bb13a13bc8803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Mon, 27 Nov 2023 09:47:34 +0100 Subject: [PATCH 10/14] Support IntegralAP bit casts --- clang/lib/AST/Interp/Boolean.h | 5 ++--- clang/lib/AST/Interp/ByteCodeExprGen.cpp | 4 +++- clang/lib/AST/Interp/Integral.h | 3 ++- clang/lib/AST/Interp/IntegralAP.h | 19 ++++++++++++++++++- clang/lib/AST/Interp/Interp.h | 9 +++++---- clang/lib/AST/Interp/InterpBitcast.cpp | 14 +++++++++----- clang/lib/AST/Interp/Opcodes.td | 4 ++-- clang/test/AST/Interp/builtin-bit-cast.cpp | 13 +++++++++++++ 8 files changed, 54 insertions(+), 17 deletions(-) diff --git a/clang/lib/AST/Interp/Boolean.h b/clang/lib/AST/Interp/Boolean.h index 0708294dab95a..fca5e56fda18d 100644 --- a/clang/lib/AST/Interp/Boolean.h +++ b/clang/lib/AST/Interp/Boolean.h @@ -16,8 +16,6 @@ #include "llvm/ADT/APSInt.h" #include "llvm/Support/MathExtras.h" #include "llvm/Support/raw_ostream.h" -#include -#include namespace clang { namespace interp { @@ -114,7 +112,8 @@ class Boolean final { return Boolean(!Value.isZero()); } - static Boolean bitcastFromMemory(const std::byte *Buff) { + static Boolean bitcastFromMemory(const std::byte *Buff, unsigned BitWidth) { + assert(BitWidth == 8); bool Val = static_cast(*Buff); return Boolean(Val); } diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp index efe79c84c1151..9b42856463fc6 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -130,8 +130,10 @@ bool ByteCodeExprGen::emitBuiltinBitCast(const CastExpr *E) { 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(), E)) + if (!this->emitBitCast(*ToT, ToTypeIsUChar || ToType->isStdByteType(), + ResultBitWidth, E)) return false; if (DiscardResult) diff --git a/clang/lib/AST/Interp/Integral.h b/clang/lib/AST/Interp/Integral.h index bcef75bbbaeb8..45cd37a5867a6 100644 --- a/clang/lib/AST/Interp/Integral.h +++ b/clang/lib/AST/Interp/Integral.h @@ -193,7 +193,8 @@ template class Integral final { return Integral(Value); } - static Integral bitcastFromMemory(const std::byte *Buff) { + static Integral bitcastFromMemory(const std::byte *Buff, unsigned BitWidth) { + assert(BitWidth == sizeof(ReprT) * 8); ReprT V; std::memcpy(&V, Buff, sizeof(ReprT)); diff --git a/clang/lib/AST/Interp/IntegralAP.h b/clang/lib/AST/Interp/IntegralAP.h index 9019f32e6cef2..bfa29682f977b 100644 --- a/clang/lib/AST/Interp/IntegralAP.h +++ b/clang/lib/AST/Interp/IntegralAP.h @@ -15,6 +15,7 @@ #include "clang/AST/APValue.h" #include "clang/AST/ComparisonCategories.h" +#include "llvm/ADT/APInt.h" #include "llvm/ADT/APSInt.h" #include "llvm/Support/MathExtras.h" #include "llvm/Support/raw_ostream.h" @@ -61,7 +62,7 @@ template class IntegralAP final { IntegralAP(APInt V) : V(V) {} /// Arbitrary value for uninitialized variables. - IntegralAP() : IntegralAP(-1, 1024) {} + IntegralAP() : IntegralAP(-1, 17) {} IntegralAP operator-() const { return IntegralAP(-V); } IntegralAP operator-(const IntegralAP &Other) const { @@ -123,6 +124,11 @@ template class IntegralAP final { } constexpr unsigned bitWidth() const { return V.getBitWidth(); } + constexpr unsigned objectReprBits() { return bitWidth(); } + constexpr unsigned valueReprBits(const ASTContext &Ctx) { return bitWidth(); } + constexpr unsigned valueReprBytes(const ASTContext &Ctx) { + return Ctx.toCharUnitsFromBits(bitWidth()).getQuantity(); + } APSInt toAPSInt(unsigned Bits = 0) const { if (Bits == 0) @@ -145,6 +151,17 @@ template class IntegralAP final { unsigned countLeadingZeros() const { return V.countl_zero(); } + static IntegralAP bitcastFromMemory(const std::byte *Buff, + unsigned BitWidth) { + APInt V(BitWidth, static_cast(0), Signed); + llvm::LoadIntFromMemory(V, (const uint8_t *)Buff, BitWidth / 8); + return IntegralAP(V); + } + + void bitcastToMemory(std::byte *Buff) const { + llvm::StoreIntToMemory(V, (uint8_t *)Buff, bitWidth() / 8); + } + void print(llvm::raw_ostream &OS) const { OS << V; } std::string toDiagnosticString(const ASTContext &Ctx) const { std::string NameStr; diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h index 5d69d59053dc5..79c35d90954b9 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -1577,11 +1577,12 @@ template bool Cast(InterpState &S, CodePtr OpPC) { } template ::T> -bool BitCast(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte) { +bool BitCast(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte, + uint32_t ResultBitWidth) { const Pointer &FromPtr = S.Stk.pop(); - size_t BuffSize = ToT::valueReprBytes(S.getCtx()); - std::vector Buff(BuffSize); + size_t BuffSize = ResultBitWidth / 8; + llvm::SmallVector Buff(BuffSize); bool HasIndeterminateBits = false; if (!DoBitCast(S, OpPC, FromPtr, Buff.data(), BuffSize, HasIndeterminateBits)) @@ -1590,7 +1591,7 @@ bool BitCast(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte) { if (!CheckBitcast(S, OpPC, HasIndeterminateBits, TargetIsUCharOrByte)) return false; - S.Stk.push(ToT::bitcastFromMemory(Buff.data())); + S.Stk.push(ToT::bitcastFromMemory(Buff.data(), ResultBitWidth)); return true; } diff --git a/clang/lib/AST/Interp/InterpBitcast.cpp b/clang/lib/AST/Interp/InterpBitcast.cpp index ea60aaa87b54d..baa2e5680ee18 100644 --- a/clang/lib/AST/Interp/InterpBitcast.cpp +++ b/clang/lib/AST/Interp/InterpBitcast.cpp @@ -32,6 +32,8 @@ using DataFunc = 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"); \ @@ -51,6 +53,8 @@ using DataFunc = 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: \ @@ -384,10 +388,10 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(P.getType()); const std::byte *M = Bytes.getBytes(BitOffset, 1234); - std::byte *Buff = (std::byte *)std::malloc( - ObjectReprChars.getQuantity()); //[sizeof(T)]; + std::byte *Buff = + (std::byte *)std::malloc(ObjectReprChars.getQuantity()); std::memcpy(Buff, M, ObjectReprChars.getQuantity()); - // Val.bitcastToMemory(Buff); + if (BigEndian) swapBytes(Buff, ObjectReprChars.getQuantity()); @@ -407,7 +411,7 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, BITCAST_TYPE_SWITCH(T, { T &Val = P.deref(); - size_t ValueReprBits = T::valueReprBits(ASTCtx); + size_t ValueReprBits = Val.valueReprBits(ASTCtx); // Check if any of the bits we're about to read are uninitialized. bool HasIndeterminateBits = !Bytes.allInitialized(BitOffset, ValueReprBits); @@ -440,7 +444,7 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, if (BigEndian) { swapBytes(Copy, ValueReprBits / 8); } - Val = T::bitcastFromMemory(Copy); + Val = T::bitcastFromMemory(Copy, Val.bitWidth()); std::free(Copy); if (!HasIndeterminateBits) diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td index 2c1e39621d79f..ff7ecbb96e1a0 100644 --- a/clang/lib/AST/Interp/Opcodes.td +++ b/clang/lib/AST/Interp/Opcodes.td @@ -586,13 +586,13 @@ def Cast: Opcode { } def BitCastTypeClass : TypeClass { - let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, Bool]; + let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, IntAP, IntAPS, Bool]; } def BitCast : Opcode { let Types = [BitCastTypeClass]; let HasGroup = 1; - let Args = [ArgBool]; + let Args = [ArgBool, ArgUint32]; } def BitCastPtr : Opcode; diff --git a/clang/test/AST/Interp/builtin-bit-cast.cpp b/clang/test/AST/Interp/builtin-bit-cast.cpp index da9115787d75b..729633fc14298 100644 --- a/clang/test/AST/Interp/builtin-bit-cast.cpp +++ b/clang/test/AST/Interp/builtin-bit-cast.cpp @@ -74,6 +74,19 @@ constexpr int foo() { } static_assert(foo() == 1, ""); + +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 Ints { static_assert(round_trip((int)-1)); static_assert(round_trip((int)0x12345678)); From 2988205c9b192b1cebf30cae256d5bb43d7bad1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Mon, 27 Nov 2023 09:53:20 +0100 Subject: [PATCH 11/14] Address other review comments --- clang/lib/AST/Interp/InterpBitcast.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/clang/lib/AST/Interp/InterpBitcast.cpp b/clang/lib/AST/Interp/InterpBitcast.cpp index baa2e5680ee18..3b5833604a469 100644 --- a/clang/lib/AST/Interp/InterpBitcast.cpp +++ b/clang/lib/AST/Interp/InterpBitcast.cpp @@ -272,7 +272,7 @@ static bool CheckBitcastType(InterpState &S, CodePtr OpPC, QualType T, } static bool readPointerToBuffer(const Context &Ctx, const Pointer &FromPtr, - BitTracker &Bits, bool RetrnOnUninit) { + BitTracker &Bits, bool ReturnOnUninit) { const ASTContext &ASTCtx = Ctx.getASTContext(); uint64_t PointerSizeInBits = ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default); @@ -284,7 +284,7 @@ static bool readPointerToBuffer(const Context &Ctx, const Pointer &FromPtr, if (!P.isInitialized()) { Bits.markUninitializedUntil(BitOffset + (primSize(T) * 8)); /// primSize() usage. - return RetrnOnUninit; + return ReturnOnUninit; } assert(P.isInitialized()); @@ -337,7 +337,8 @@ bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff, bool BigEndian = ASTCtx.getTargetInfo().isBigEndian(); BitTracker Bits; - bool Success = readPointerToBuffer(S.getContext(), P, Bits, false); + bool Success = + readPointerToBuffer(S.getContext(), P, Bits, /*ReturnOnUninit=*/false); Bits.markUninitializedUntil(BuffSize * 8); assert(Bits.size() == BuffSize * 8); @@ -350,13 +351,9 @@ bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff, return Success; // && !HasIndeterminateBits; } -// 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 +/// Bitcast from a Pointer to a Pointer. +/// We read all fields from \p P into a buffer, then iterate +/// over the fields of \p DestPtr and read from the buffer. bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, CodePtr OpPC) { assert(P.isLive()); @@ -376,7 +373,7 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, bool BigEndian = ASTCtx.getTargetInfo().isBigEndian(); BitTracker Bytes; - if (!readPointerToBuffer(Ctx, P, Bytes, true)) + if (!readPointerToBuffer(Ctx, P, Bytes, /*ReturnOnUninit=*/true)) return false; bool Success = enumeratePointerFields( From f3a05e4a765efc0689de85a177f496eeba5cb0db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Mon, 27 Nov 2023 10:52:36 +0100 Subject: [PATCH 12/14] Rename a member and address more review comments. --- clang/lib/AST/Interp/InterpBitcast.cpp | 30 ++++++++++++++------------ 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/clang/lib/AST/Interp/InterpBitcast.cpp b/clang/lib/AST/Interp/InterpBitcast.cpp index 3b5833604a469..084c4a9e46da4 100644 --- a/clang/lib/AST/Interp/InterpBitcast.cpp +++ b/clang/lib/AST/Interp/InterpBitcast.cpp @@ -73,18 +73,18 @@ static void swapBytes(std::byte *M, size_t N) { /// All offsets are in bits. struct BitTracker { llvm::BitVector Initialized; - llvm::BitVector Data_; + llvm::BitVector Data; BitTracker() = default; size_t size() const { - assert(Initialized.size() == Data_.size()); + assert(Initialized.size() == Data.size()); return Initialized.size(); } const std::byte *getBytes(size_t BitOffset, int a) { assert(BitOffset % 8 == 0); - return reinterpret_cast(Data_.getData().data()) + + return reinterpret_cast(Data.getData().data()) + (BitOffset / 8); } @@ -95,11 +95,14 @@ struct BitTracker { bool allInitialized() const { return Initialized.all(); } void pushData(const std::byte *data, size_t BitOffset, size_t BitWidth) { - assert(BitOffset >= Data_.size()); + assert(BitOffset >= Data.size()); + Data.reserve(BitOffset + BitWidth); + Initialized.reserve(BitOffset + BitWidth); + // First, fill up the bit vector until BitOffset. The bits are all 0 // but we record them as indeterminate. { - Data_.resize(BitOffset, false); + Data.resize(BitOffset, false); Initialized.resize(BitOffset, false); } @@ -107,7 +110,7 @@ struct BitTracker { // Read all full bytes first for (size_t I = 0; I != BitWidth / 8; ++I) { for (unsigned X = 0; X != 8; ++X) { - Data_.push_back((data[I] & std::byte(1 << X)) != std::byte{0}); + Data.push_back((data[I] & std::byte(1 << X)) != std::byte{0}); Initialized.push_back(true); ++BitsHandled; } @@ -116,21 +119,21 @@ struct BitTracker { // Rest of the bits. assert((BitWidth - BitsHandled) < 8); for (size_t I = 0, E = (BitWidth - BitsHandled); I != E; ++I) { - Data_.push_back((data[BitWidth / 8] & std::byte(1 << I)) != std::byte{0}); + Data.push_back((data[BitWidth / 8] & std::byte(1 << I)) != std::byte{0}); Initialized.push_back(true); ++BitsHandled; } } void pushZeroes(size_t Amount) { - size_t N = Data_.size(); - Data_.resize(N + Amount, false); + size_t N = Data.size(); + Data.resize(N + Amount, false); Initialized.resize(N + Amount, true); } void markUninitializedUntil(size_t Offset) { - assert(Offset >= Data_.size()); - Data_.resize(Offset, false); + assert(Offset >= Data.size()); + Data.resize(Offset, false); Initialized.resize(Offset, false); } }; @@ -185,7 +188,6 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset, size_t BitOffset = Offset + Ctx.getASTContext().toBits(ByteOffset); Ok = Ok && enumerateData(Elem, Ctx, BitOffset, F); } - // TODO: Virtual bases? for (unsigned I = 0; I != R->getNumFields(); ++I) { const Record::Field *Fi = R->getField(I); @@ -344,11 +346,11 @@ bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff, assert(Bits.size() == BuffSize * 8); HasIndeterminateBits = !Bits.allInitialized(); - std::memcpy(Buff, Bits.Data_.getData().data(), BuffSize); + std::memcpy(Buff, Bits.Data.getData().data(), BuffSize); if (BigEndian) swapBytes(Buff, BuffSize); - return Success; // && !HasIndeterminateBits; + return Success; } /// Bitcast from a Pointer to a Pointer. From 48c0eeb088ed0a4c5479404af8b5b75b8bf81b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Mon, 27 Nov 2023 11:07:55 +0100 Subject: [PATCH 13/14] Handle BitCast and BitCastFP opcodes the same --- clang/lib/AST/Interp/ByteCodeExprGen.cpp | 15 ++++-------- clang/lib/AST/Interp/Interp.h | 31 +++++++----------------- clang/lib/AST/Interp/Opcodes.td | 9 ++----- 3 files changed, 16 insertions(+), 39 deletions(-) diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp index 9b42856463fc6..bf22d423320fd 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -117,23 +117,18 @@ bool ByteCodeExprGen::emitBuiltinBitCast(const CastExpr *E) { 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. - // - // 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)); uint32_t ResultBitWidth = std::max(Ctx.getBitWidth(ToType), 8u); if (!this->emitBitCast(*ToT, ToTypeIsUChar || ToType->isStdByteType(), - ResultBitWidth, E)) + ResultBitWidth, TargetSemantics, E)) return false; if (DiscardResult) diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h index 79c35d90954b9..faf4962044328 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -1578,7 +1578,8 @@ template bool Cast(InterpState &S, CodePtr OpPC) { template ::T> bool BitCast(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte, - uint32_t ResultBitWidth) { + uint32_t ResultBitWidth, const llvm::fltSemantics *Sem) { + assert(ResultBitWidth > 0); const Pointer &FromPtr = S.Stk.pop(); size_t BuffSize = ResultBitWidth / 8; @@ -1591,27 +1592,13 @@ bool BitCast(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte, if (!CheckBitcast(S, OpPC, HasIndeterminateBits, TargetIsUCharOrByte)) return false; - S.Stk.push(ToT::bitcastFromMemory(Buff.data(), ResultBitWidth)); - 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); - bool HasIndeterminateBits = false; - - if (!DoBitCast(S, OpPC, FromPtr, Buff.data(), TargetSize, - HasIndeterminateBits)) - return false; - - if (!CheckBitcast(S, OpPC, HasIndeterminateBits, - /*TargetIsUCharOrByte=*/false)) - return false; - - S.Stk.push(Floating::bitcastFromMemory(Buff.data(), *Sem)); + if constexpr (std::is_same_v) { + assert(Sem); + S.Stk.push(Floating::bitcastFromMemory(Buff.data(), *Sem)); + } else { + assert(!Sem); + S.Stk.push(ToT::bitcastFromMemory(Buff.data(), ResultBitWidth)); + } return true; } diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td index ff7ecbb96e1a0..76f10318f6699 100644 --- a/clang/lib/AST/Interp/Opcodes.td +++ b/clang/lib/AST/Interp/Opcodes.td @@ -586,22 +586,17 @@ def Cast: Opcode { } def BitCastTypeClass : TypeClass { - let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, IntAP, IntAPS, Bool]; + let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, IntAP, IntAPS, Bool, Float]; } def BitCast : Opcode { let Types = [BitCastTypeClass]; let HasGroup = 1; - let Args = [ArgBool, ArgUint32]; + let Args = [ArgBool, ArgUint32, ArgFltSemantics]; } def BitCastPtr : Opcode; -def BitCastFP : Opcode { - let Types = []; - let Args = [ArgFltSemantics, ArgUint32]; -} - def CastFP : Opcode { let Types = []; let Args = [ArgFltSemantics, ArgRoundingMode]; From 2caadff9807b2e4399c4404338246ac9e574f704 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timm=20B=C3=A4der?= Date: Mon, 27 Nov 2023 16:45:51 +0100 Subject: [PATCH 14/14] switch from malloc+free to llvm::SmallVector --- clang/lib/AST/Interp/InterpBitcast.cpp | 38 +++++++++++--------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/clang/lib/AST/Interp/InterpBitcast.cpp b/clang/lib/AST/Interp/InterpBitcast.cpp index 084c4a9e46da4..7626bd71f88c1 100644 --- a/clang/lib/AST/Interp/InterpBitcast.cpp +++ b/clang/lib/AST/Interp/InterpBitcast.cpp @@ -311,14 +311,11 @@ static bool readPointerToBuffer(const Context &Ctx, const Pointer &FromPtr, unsigned ValueReprBytes = Val.valueReprBytes(ASTCtx); assert(ObjectReprBytes >= ValueReprBytes); - std::byte *Buff = - (std::byte *)std::malloc(ObjectReprChars.getQuantity()); - Val.bitcastToMemory(Buff); - + llvm::SmallVector Buff(ObjectReprChars.getQuantity()); + Val.bitcastToMemory(Buff.data()); if (BigEndian) - swapBytes(Buff, BitWidth / 8); - Bits.pushData(Buff, BitOffset, BitWidth); - std::free(Buff); + swapBytes(Buff.data(), BitWidth / 8); + Bits.pushData(Buff.data(), BitOffset, BitWidth); }); return true; }); @@ -386,17 +383,16 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, const auto &Sem = ASTCtx.getFloatTypeSemantics(FloatType); CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(P.getType()); - const std::byte *M = Bytes.getBytes(BitOffset, 1234); - std::byte *Buff = - (std::byte *)std::malloc(ObjectReprChars.getQuantity()); - std::memcpy(Buff, M, ObjectReprChars.getQuantity()); + llvm::SmallVector Buff(ObjectReprChars.getQuantity()); + const std::byte *M = + Bytes.getBytes(BitOffset, ObjectReprChars.getQuantity()); + std::memcpy(Buff.data(), M, ObjectReprChars.getQuantity()); if (BigEndian) - swapBytes(Buff, ObjectReprChars.getQuantity()); + swapBytes(Buff.data(), ObjectReprChars.getQuantity()); - P.deref() = Floating::bitcastFromMemory(Buff, Sem); + P.deref() = Floating::bitcastFromMemory(Buff.data(), Sem); P.initialize(); - std::free(Buff); return true; } if (T == PT_Ptr) { @@ -435,16 +431,14 @@ bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr, assert(!P.getField()->isBitField()); - std::byte *Copy = (std::byte *)std::malloc(ValueReprBits / 8); - const std::byte *M = Bytes.getBytes(BitOffset, 1234); + llvm::SmallVector Copy(ValueReprBits / 8); + const std::byte *M = Bytes.getBytes(BitOffset, ValueReprBits / 8); - std::memcpy(Copy, M, ValueReprBits / 8); + std::memcpy(Copy.data(), M, ValueReprBits / 8); - if (BigEndian) { - swapBytes(Copy, ValueReprBits / 8); - } - Val = T::bitcastFromMemory(Copy, Val.bitWidth()); - std::free(Copy); + if (BigEndian) + swapBytes(Copy.data(), ValueReprBits / 8); + Val = T::bitcastFromMemory(Copy.data(), Val.bitWidth()); if (!HasIndeterminateBits) P.initialize();