llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang <details> <summary>Changes</summary> >From https://reviews.llvm.org/D154951 - which is never gonna make it out of >Phabricator anyway. The tests are failing because https://reviews.llvm.org/D154581 is not pushed. Aaron's review mentioned that we should support bitfields before pushing this. --- Patch is 56.88 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/68288.diff 13 Files Affected: - (modified) clang/lib/AST/CMakeLists.txt (+1) - (modified) clang/lib/AST/Interp/Boolean.h (+13-2) - (modified) clang/lib/AST/Interp/ByteCodeExprGen.cpp (+71) - (modified) clang/lib/AST/Interp/ByteCodeExprGen.h (+1) - (modified) clang/lib/AST/Interp/Floating.h (+11) - (modified) clang/lib/AST/Interp/Integral.h (+18-1) - (modified) clang/lib/AST/Interp/Interp.cpp (+17) - (modified) clang/lib/AST/Interp/Interp.h (+66) - (added) clang/lib/AST/Interp/InterpBitcast.cpp (+482) - (modified) clang/lib/AST/Interp/Opcodes.td (+17) - (modified) clang/lib/AST/Interp/PrimType.h (+4) - (added) clang/test/AST/Interp/builtin-bit-cast.cpp (+683) - (modified) clang/test/AST/Interp/literals.cpp (+5) ``````````diff diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index fe3f8c485ec1c56..5a188b583022bda 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 c3ed3d61f76ca1c..d395264ab2de448 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 <cstddef> -#include <cstdint> #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 <cstddef> +#include <cstdint> namespace clang { namespace interp { @@ -65,6 +66,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(); } @@ -106,6 +110,13 @@ class Boolean final { return Boolean(!Value.isZero()); } + static Boolean bitcastFromMemory(const std::byte *Buff) { + bool Val = static_cast<bool>(*Buff); + return Boolean(Val); + } + + void bitcastToMemory(std::byte *Buff) { std::memcpy(Buff, &V, sizeof(V)); } + static Boolean zero() { return from(false); } template <typename T> diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp index 3cf8bf874b1d210..8cc010d6f100ccb 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -67,6 +67,74 @@ template <class Emitter> 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<T> is false; +// (3.2) - is_pointer_v<T> is false; +// (3.3) - is_member_pointer_v<T> is false; +// (3.4) - is_volatile_v<T> is false; and +// (3.5) - T has no non-static data members of reference type +template <class Emitter> +bool ByteCodeExprGen<Emitter>::emitBuiltinBitCast(const CastExpr *E) { + const Expr *SubExpr = E->getSubExpr(); + QualType FromType = SubExpr->getType(); + QualType ToType = E->getType(); + std::optional<PrimType> ToT = classify(ToType); + + // FIXME: This is wrong. We need to do the bitcast and then + // throw away the result, so we still get the diagnostics. + if (DiscardResult) + return this->discard(SubExpr); + + if (ToType->isNullPtrType()) { + if (!this->discard(SubExpr)) + return false; + + return this->emitNullPtr(E); + } + + if (FromType->isNullPtrType() && ToT) { + if (!this->discard(SubExpr)) + return false; + + return visitZeroInitializer(ToType, E); + } + assert(!ToType->isReferenceType()); + + // Get a pointer to the value-to-cast on the stack. + if (!this->visit(SubExpr)) + return false; + + if (!ToT || ToT == PT_Ptr) { + // Conversion to an array or record type. + return this->emitBitCastPtr(E); + } + + assert(ToT); + + // Conversion to a primitive type. FromType can be another + // primitive type, or a record/array. + // + // Same thing for floats, but we need the target + // semantics here. + if (ToT == PT_Float) { + const auto *TargetSemantics = &Ctx.getFloatSemantics(ToType); + CharUnits FloatSize = Ctx.getASTContext().getTypeSizeInChars(ToType); + return this->emitBitCastFP(TargetSemantics, FloatSize.getQuantity(), E); + } + + bool ToTypeIsUChar = (ToType->isSpecificBuiltinType(BuiltinType::UChar) || + ToType->isSpecificBuiltinType(BuiltinType::Char_U)); + + if (!this->emitBitCast(*ToT, ToTypeIsUChar || ToType->isStdByteType(), E)) + return false; + + if (DiscardResult) + return this->emitPop(*ToT, E); + + return true; +} + template <class Emitter> bool ByteCodeExprGen<Emitter>::VisitCastExpr(const CastExpr *CE) { auto *SubExpr = CE->getSubExpr(); @@ -87,6 +155,9 @@ bool ByteCodeExprGen<Emitter>::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 47a3f75f13459d0..8f04c4809ccf41d 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.h +++ b/clang/lib/AST/Interp/ByteCodeExprGen.h @@ -280,6 +280,7 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, 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 a22b3fa79f3992f..727d37520047b77 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(); } @@ -133,6 +140,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 4dbe9c9bcb14b43..9c78bb04876ea02 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" @@ -116,6 +117,10 @@ template <unsigned Bits, bool Signed> 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; } @@ -182,6 +187,18 @@ template <unsigned Bits, bool Signed> 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<ReprT, Min, Max>(Value); } diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp index 8e851595963184c..d2c65daafc7d135 100644 --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -576,7 +576,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 d62e64bedb213ac..c14f890e188ecdb 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -183,6 +183,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); @@ -194,6 +197,18 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F, bool InterpretOffsetOf(InterpState &S, CodePtr OpPC, const OffsetOfExpr *E, llvm::ArrayRef<int64_t> 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 }; //===----------------------------------------------------------------------===// @@ -1557,6 +1572,57 @@ template <PrimType TIn, PrimType TOut> bool Cast(InterpState &S, CodePtr OpPC) { return true; } +template <PrimType Name, class ToT = typename PrimConv<Name>::T> +bool BitCast(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte) { + const Pointer &FromPtr = S.Stk.pop<Pointer>(); + + size_t BuffSize = ToT::valueReprBytes(S.getCtx()); + std::vector<std::byte> 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>(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<Pointer>(); + + std::vector<std::byte> 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>(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>(); + Pointer &ToPtr = S.Stk.peek<Pointer>(); + + // 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 000000000000000..91326fc8e788080 --- /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<bool(const Pointer &P, PrimType Ty, size_t Offset)>; + +#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<bool> Initialized; + std::vector<std::byte> 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 ... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/68288 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits