[clang] [clang][bytecode] Start implementing __builtin_bit_cast (PR #112126)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Sun Oct 13 23:50:34 PDT 2024
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/112126
>From 17c346706a58342097170bdfac0aabd8c095994e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Sun, 13 Oct 2024 08:57:48 +0200
Subject: [PATCH] [clang][bytecode] Start implementing __builtin_bit_cast
---
clang/lib/AST/ByteCode/Boolean.h | 8 +
clang/lib/AST/ByteCode/Compiler.cpp | 64 ++++
clang/lib/AST/ByteCode/Compiler.h | 1 +
clang/lib/AST/ByteCode/Floating.h | 5 +
clang/lib/AST/ByteCode/Integral.h | 12 +
clang/lib/AST/ByteCode/IntegralAP.h | 6 +
clang/lib/AST/ByteCode/Interp.cpp | 17 +
clang/lib/AST/ByteCode/Interp.h | 30 ++
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 1 +
.../lib/AST/ByteCode/InterpBuiltinBitCast.cpp | 336 +++++++++++++++++
clang/lib/AST/ByteCode/InterpBuiltinBitCast.h | 26 ++
clang/lib/AST/ByteCode/Opcodes.td | 6 +
clang/lib/AST/CMakeLists.txt | 1 +
clang/test/AST/ByteCode/builtin-bit-cast.cpp | 348 ++++++++++++++++++
14 files changed, 861 insertions(+)
create mode 100644 clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
create mode 100644 clang/lib/AST/ByteCode/InterpBuiltinBitCast.h
create mode 100644 clang/test/AST/ByteCode/builtin-bit-cast.cpp
diff --git a/clang/lib/AST/ByteCode/Boolean.h b/clang/lib/AST/ByteCode/Boolean.h
index c568b557574e2b..14c61686d6c692 100644
--- a/clang/lib/AST/ByteCode/Boolean.h
+++ b/clang/lib/AST/ByteCode/Boolean.h
@@ -81,6 +81,14 @@ class Boolean final {
Boolean truncate(unsigned TruncBits) const { return *this; }
+ static Boolean bitcastFromMemory(const std::byte *Buff, unsigned BitWidth) {
+ assert(BitWidth == 8);
+ bool Val = static_cast<bool>(*Buff);
+ return Boolean(Val);
+ }
+
+ void bitcastToMemory(std::byte *Buff) { std::memcpy(Buff, &V, sizeof(V)); }
+
void print(llvm::raw_ostream &OS) const { OS << (V ? "true" : "false"); }
std::string toDiagnosticString(const ASTContext &Ctx) const {
std::string NameStr;
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index b2663714340b93..4315f77b0581f9 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -470,6 +470,9 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *CE) {
return this->emitDecayPtr(*FromT, *ToT, CE);
}
+ case CK_LValueToRValueBitCast:
+ return this->emitBuiltinBitCast(CE);
+
case CK_IntegralToBoolean:
case CK_FixedPointToBoolean:
case CK_BooleanToSignedIntegral:
@@ -6400,6 +6403,67 @@ bool Compiler<Emitter>::emitDestruction(const Descriptor *Desc,
return this->emitRecordDestruction(Desc->ElemRecord, Loc);
}
+// This function is constexpr if and only if To, From, and the types of
+// all subobjects of To and From are types T such that...
+// (3.1) - is_union_v<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 Compiler<Emitter>::emitBuiltinBitCast(const CastExpr *E) {
+ const Expr *SubExpr = E->getSubExpr();
+ QualType FromType = SubExpr->getType();
+ QualType ToType = E->getType();
+ std::optional<PrimType> ToT = classify(ToType);
+
+ assert(!DiscardResult && "Implement");
+
+ if (ToType->isNullPtrType()) {
+ if (!this->discard(SubExpr))
+ return false;
+
+ return this->emitNullPtr(nullptr, E);
+ }
+
+ if (FromType->isNullPtrType() && ToT) {
+ if (!this->discard(SubExpr))
+ return false;
+
+ return visitZeroInitializer(*ToT, ToType, E);
+ }
+ assert(!ToType->isReferenceType());
+
+ // Get a pointer to the value-to-cast on the stack.
+ if (!this->visit(SubExpr))
+ return false;
+
+ if (!ToT || ToT == PT_Ptr) {
+ // Conversion to an array or record type.
+ assert(false && "Implement");
+ }
+ assert(ToT);
+
+ const llvm::fltSemantics *TargetSemantics = nullptr;
+ if (ToT == PT_Float)
+ TargetSemantics = &Ctx.getFloatSemantics(ToType);
+
+ // Conversion to a primitive type. FromType can be another
+ // primitive type, or a record/array.
+ bool ToTypeIsUChar = (ToType->isSpecificBuiltinType(BuiltinType::UChar) ||
+ ToType->isSpecificBuiltinType(BuiltinType::Char_U));
+ uint32_t ResultBitWidth = std::max(Ctx.getBitWidth(ToType), 8u);
+
+ if (!this->emitBitCast(*ToT, ToTypeIsUChar || ToType->isStdByteType(),
+ ResultBitWidth, TargetSemantics, E))
+ return false;
+
+ if (DiscardResult)
+ return this->emitPop(*ToT, E);
+
+ return true;
+}
+
namespace clang {
namespace interp {
diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index 4253e7b3248c9f..5fbde244b1d9e9 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -373,6 +373,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
unsigned collectBaseOffset(const QualType BaseType,
const QualType DerivedType);
bool emitLambdaStaticInvokerBody(const CXXMethodDecl *MD);
+ bool emitBuiltinBitCast(const CastExpr *E);
bool compileConstructor(const CXXConstructorDecl *Ctor);
bool compileDestructor(const CXXDestructorDecl *Dtor);
diff --git a/clang/lib/AST/ByteCode/Floating.h b/clang/lib/AST/ByteCode/Floating.h
index 114487821880fb..be38e6991dad75 100644
--- a/clang/lib/AST/ByteCode/Floating.h
+++ b/clang/lib/AST/ByteCode/Floating.h
@@ -135,6 +135,11 @@ class Floating final {
return Floating(APFloat(Sem, API));
}
+ void bitcastToMemory(std::byte *Buff) {
+ llvm::APInt API = F.bitcastToAPInt();
+ llvm::StoreIntToMemory(API, (uint8_t *)Buff, bitWidth() / 8);
+ }
+
// === Serialization support ===
size_t bytesToSerialize() const {
return sizeof(llvm::fltSemantics *) +
diff --git a/clang/lib/AST/ByteCode/Integral.h b/clang/lib/AST/ByteCode/Integral.h
index e06ec1669259da..9b07e86bbc8dd9 100644
--- a/clang/lib/AST/ByteCode/Integral.h
+++ b/clang/lib/AST/ByteCode/Integral.h
@@ -151,6 +151,18 @@ template <unsigned Bits, bool Signed> class Integral final {
return Compare(V, RHS.V);
}
+ void bitcastToMemory(std::byte *Dest) const {
+ std::memcpy(Dest, &V, sizeof(V));
+ }
+
+ static Integral bitcastFromMemory(const std::byte *Src, unsigned BitWidth) {
+ assert(BitWidth == sizeof(ReprT) * 8);
+ ReprT V;
+
+ std::memcpy(&V, Src, sizeof(ReprT));
+ return Integral(V);
+ }
+
std::string toDiagnosticString(const ASTContext &Ctx) const {
std::string NameStr;
llvm::raw_string_ostream OS(NameStr);
diff --git a/clang/lib/AST/ByteCode/IntegralAP.h b/clang/lib/AST/ByteCode/IntegralAP.h
index a4d656433344b7..58e413e4d06c12 100644
--- a/clang/lib/AST/ByteCode/IntegralAP.h
+++ b/clang/lib/AST/ByteCode/IntegralAP.h
@@ -173,6 +173,12 @@ template <bool Signed> class IntegralAP final {
return IntegralAP<false>(Copy);
}
+ void bitcastToMemory(std::byte *Dest) const { assert(false); }
+
+ static IntegralAP bitcastFromMemory(const std::byte *Src, unsigned BitWidth) {
+ return IntegralAP();
+ }
+
ComparisonCategoryResult compare(const IntegralAP &RHS) const {
assert(Signed == RHS.isSigned());
assert(bitWidth() == RHS.bitWidth());
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 95715655cc9bbd..0f830076f131af 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1539,6 +1539,23 @@ bool CastPointerIntegralAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth) {
return true;
}
+bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits,
+ bool TargetIsUCharOrByte) {
+ // This is always fine.
+ if (!HasIndeterminateBits)
+ return true;
+
+ // Indeterminate bits can only be bitcast to unsigned char or std::byte.
+ if (TargetIsUCharOrByte)
+ return true;
+
+ const Expr *E = S.Current->getExpr(OpPC);
+ QualType ExprType = E->getType();
+ S.FFDiag(E, diag::note_constexpr_bit_cast_indet_dest)
+ << ExprType << S.getLangOpts().CharIsSigned << E->getSourceRange();
+ return false;
+}
+
// https://github.com/llvm/llvm-project/issues/102513
#if defined(_WIN32) && !defined(__clang__) && !defined(NDEBUG)
#pragma optimize("", off)
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index dece95971b7617..e41fe1b2e55e08 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -20,6 +20,7 @@
#include "Floating.h"
#include "Function.h"
#include "FunctionPointer.h"
+#include "InterpBuiltinBitCast.h"
#include "InterpFrame.h"
#include "InterpStack.h"
#include "InterpState.h"
@@ -162,6 +163,8 @@ bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
const CallExpr *CE);
bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T);
bool InvalidShuffleVectorIndex(InterpState &S, CodePtr OpPC, uint32_t Index);
+bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits,
+ bool TargetIsUCharOrByte);
template <typename T>
static bool handleOverflow(InterpState &S, CodePtr OpPC, const T &SrcValue) {
@@ -2995,6 +2998,33 @@ bool CheckNewTypeMismatchArray(InterpState &S, CodePtr OpPC, const Expr *E) {
return CheckNewTypeMismatch(S, OpPC, E, static_cast<uint64_t>(Size));
}
bool InvalidNewDeleteExpr(InterpState &S, CodePtr OpPC, const Expr *E);
+
+template <PrimType Name, class T = typename PrimConv<Name>::T>
+inline bool BitCast(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte,
+ uint32_t ResultBitWidth, const llvm::fltSemantics *Sem) {
+ const Pointer &FromPtr = S.Stk.pop<Pointer>();
+
+ size_t BuffSize = ResultBitWidth / 8;
+ llvm::SmallVector<std::byte> Buff(BuffSize);
+ bool HasIndeterminateBits = false;
+
+ if (!DoBitCast(S, OpPC, FromPtr, Buff.data(), BuffSize, HasIndeterminateBits))
+ return false;
+
+ if (!CheckBitCast(S, OpPC, HasIndeterminateBits, TargetIsUCharOrByte))
+ return false;
+
+ if constexpr (std::is_same_v<T, Floating>) {
+ assert(false && "Implement");
+ // assert(Sem);
+ // S.Stk.push<Floating>(Floating::bitcastFromMemory(Buff.data(), *Sem));
+ } else {
+ assert(!Sem);
+ S.Stk.push<T>(T::bitcastFromMemory(Buff.data(), ResultBitWidth));
+ }
+ return true;
+}
+
//===----------------------------------------------------------------------===//
// Read opcode arguments
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index ec27aebf84bd80..d0d569cb46381f 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -10,6 +10,7 @@
#include "Compiler.h"
#include "EvalEmitter.h"
#include "Interp.h"
+#include "InterpBuiltinBitCast.h"
#include "PrimType.h"
#include "clang/AST/OSLog.h"
#include "clang/AST/RecordLayout.h"
diff --git a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
new file mode 100644
index 00000000000000..656095a44c0f46
--- /dev/null
+++ b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
@@ -0,0 +1,336 @@
+//===-------------------- InterpBuiltinBitCast.cpp --------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "InterpBuiltinBitCast.h"
+#include "Boolean.h"
+#include "Context.h"
+#include "FixedPoint.h"
+#include "Floating.h"
+#include "Integral.h"
+#include "IntegralAP.h"
+#include "InterpState.h"
+#include "MemberPointer.h"
+#include "Pointer.h"
+#include "Record.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/RecordLayout.h"
+#include "clang/Basic/TargetInfo.h"
+#include "llvm/ADT/BitVector.h"
+#include <cmath>
+
+using namespace clang;
+using namespace clang::interp;
+
+/// Used to iterate over pointer fields.
+using DataFunc =
+ llvm::function_ref<bool(const Pointer &P, PrimType Ty, size_t BitOffset)>;
+
+#define BITCAST_TYPE_SWITCH(Expr, B) \
+ do { \
+ switch (Expr) { \
+ TYPE_SWITCH_CASE(PT_Sint8, B) \
+ TYPE_SWITCH_CASE(PT_Uint8, B) \
+ TYPE_SWITCH_CASE(PT_Sint16, B) \
+ TYPE_SWITCH_CASE(PT_Uint16, B) \
+ TYPE_SWITCH_CASE(PT_Sint32, B) \
+ TYPE_SWITCH_CASE(PT_Uint32, B) \
+ TYPE_SWITCH_CASE(PT_Sint64, B) \
+ TYPE_SWITCH_CASE(PT_Uint64, B) \
+ TYPE_SWITCH_CASE(PT_IntAP, B) \
+ TYPE_SWITCH_CASE(PT_IntAPS, B) \
+ TYPE_SWITCH_CASE(PT_Bool, B) \
+ default: \
+ llvm_unreachable("Unhandled bitcast type"); \
+ } \
+ } while (0)
+
+/// Float is a special case that sometimes needs the floating point semantics
+/// to be available.
+#define BITCAST_TYPE_SWITCH_WITH_FLOAT(Expr, B) \
+ do { \
+ switch (Expr) { \
+ TYPE_SWITCH_CASE(PT_Sint8, B) \
+ TYPE_SWITCH_CASE(PT_Uint8, B) \
+ TYPE_SWITCH_CASE(PT_Sint16, B) \
+ TYPE_SWITCH_CASE(PT_Uint16, B) \
+ TYPE_SWITCH_CASE(PT_Sint32, B) \
+ TYPE_SWITCH_CASE(PT_Uint32, B) \
+ TYPE_SWITCH_CASE(PT_Sint64, B) \
+ TYPE_SWITCH_CASE(PT_Uint64, B) \
+ TYPE_SWITCH_CASE(PT_IntAP, B) \
+ TYPE_SWITCH_CASE(PT_IntAPS, B) \
+ TYPE_SWITCH_CASE(PT_Bool, B) \
+ TYPE_SWITCH_CASE(PT_Float, B) \
+ default: \
+ llvm_unreachable("Unhandled bitcast type"); \
+ } \
+ } while (0)
+
+static void swapBytes(std::byte *M, size_t N) {
+ for (size_t I = 0; I != (N / 2); ++I)
+ std::swap(M[I], M[N - 1 - I]);
+}
+
+/// Track what bits have been initialized to known values and which ones
+/// have indeterminate value.
+/// All offsets are in bits.
+struct BitcastBuffer {
+ llvm::BitVector Initialized;
+ llvm::BitVector Data;
+
+ BitcastBuffer() = default;
+
+ size_t size() const {
+ assert(Initialized.size() == Data.size());
+ return Initialized.size();
+ }
+
+ const std::byte *data() const { return getBytes(0); }
+
+ const std::byte *getBytes(size_t BitOffset) const {
+ assert(BitOffset % 8 == 0);
+ return reinterpret_cast<const std::byte *>(Data.getData().data()) +
+ (BitOffset / 8);
+ }
+
+ bool allInitialized() const { return Initialized.all(); }
+
+ void pushData(const std::byte *data, size_t BitOffset, size_t BitWidth) {
+ 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);
+ Initialized.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});
+ 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;
+ }
+ }
+};
+
+/// We use this to recursively iterate over all fields and elemends of a pointer
+/// and extract relevant data for a bitcast.
+static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset,
+ DataFunc F) {
+ const Descriptor *FieldDesc = P.getFieldDesc();
+ assert(FieldDesc);
+
+ // Primitives.
+ if (FieldDesc->isPrimitive())
+ return F(P, FieldDesc->getPrimType(), Offset);
+
+ // Primitive arrays.
+ if (FieldDesc->isPrimitiveArray()) {
+ QualType ElemType =
+ 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) {
+ Ok = Ok && F(P.atIndex(I), ElemT, Offset);
+ Offset += ElemSizeInBits;
+ }
+ return Ok;
+ }
+
+ // Composite arrays.
+ if (FieldDesc->isCompositeArray()) {
+ QualType ElemType =
+ FieldDesc->getType()->getAsArrayTypeUnsafe()->getElementType();
+ size_t ElemSizeInBits = Ctx.getASTContext().getTypeSize(ElemType);
+ for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) {
+ enumerateData(P.atIndex(I).narrow(), Ctx, Offset, F);
+ Offset += ElemSizeInBits;
+ }
+ return true;
+ }
+
+ // Records.
+ if (FieldDesc->isRecord()) {
+ 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<CXXRecordDecl>(B.Decl));
+ size_t BitOffset = Offset + Ctx.getASTContext().toBits(ByteOffset);
+ Ok = Ok && enumerateData(Elem, Ctx, BitOffset, F);
+ }
+
+ for (unsigned I = 0; I != R->getNumFields(); ++I) {
+ const Record::Field *Fi = R->getField(I);
+ Pointer Elem = P.atField(Fi->Offset);
+ size_t BitOffset = Offset + Layout.getFieldOffset(I);
+ Ok = Ok && enumerateData(Elem, Ctx, BitOffset, F);
+ }
+ return Ok;
+ }
+
+ llvm_unreachable("Unhandled data type");
+}
+
+static bool enumeratePointerFields(const Pointer &P, const Context &Ctx,
+ DataFunc F) {
+ return enumerateData(P, Ctx, 0, F);
+}
+
+// This function is constexpr if and only if To, From, and the types of
+// all subobjects of To and From are types T such that...
+// (3.1) - is_union_v<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
+//
+// 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<int>(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<CXXRecordDecl>(RD)) {
+ for (const CXXBaseSpecifier &BS : CXXRD->bases()) {
+ if (!CheckBitcastType(S, OpPC, BS.getType(), IsToType))
+ return note(C_Base, BS.getType(), BS.getBeginLoc());
+ }
+ }
+ for (const FieldDecl *FD : RD->fields()) {
+ if (FD->getType()->isReferenceType())
+ return diag(E_Reference);
+ if (!CheckBitcastType(S, OpPC, FD->getType(), IsToType))
+ return note(C_Member, FD->getType(), FD->getSourceRange());
+ }
+ }
+
+ if (T->isArrayType() &&
+ !CheckBitcastType(S, OpPC, S.getASTContext().getBaseElementType(T),
+ IsToType))
+ return false;
+
+ return true;
+}
+
+static bool readPointerToBuffer(const Context &Ctx, const Pointer &FromPtr,
+ BitcastBuffer &Buffer, bool ReturnOnUninit) {
+ const ASTContext &ASTCtx = Ctx.getASTContext();
+ bool BigEndian = ASTCtx.getTargetInfo().isBigEndian();
+
+ return enumeratePointerFields(
+ FromPtr, Ctx,
+ [&](const Pointer &P, PrimType T, size_t BitOffset) -> bool {
+ if (!P.isInitialized()) {
+ assert(false && "Implement");
+ return ReturnOnUninit;
+ }
+
+ assert(P.isInitialized());
+ // nullptr_t is a PT_Ptr for us, but it's still not std::is_pointer_v.
+ if (T == PT_Ptr) {
+ assert(false && "Implement");
+ assert(P.getType()->isNullPtrType());
+ return true;
+ }
+
+ 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);
+
+ BITCAST_TYPE_SWITCH_WITH_FLOAT(T, {
+ T Val = P.deref<T>();
+
+ llvm::SmallVector<std::byte> Buff(ObjectReprChars.getQuantity());
+ Val.bitcastToMemory(Buff.data());
+ if (BigEndian)
+ swapBytes(Buff.data(), BitWidth / 8);
+ Buffer.pushData(Buff.data(), BitOffset, BitWidth);
+ });
+ return true;
+ });
+}
+
+bool clang::interp::DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+ std::byte *Buff, size_t BuffSize,
+ bool &HasIndeterminateBits) {
+
+ assert(Ptr.isLive());
+ assert(Ptr.isBlockPointer());
+
+ const ASTContext &ASTCtx = S.getASTContext();
+ bool BigEndian = ASTCtx.getTargetInfo().isBigEndian();
+
+ BitcastBuffer Buffer;
+ if (!CheckBitcastType(S, OpPC, Ptr.getType(), /*IsToType=*/false))
+ return false;
+
+ bool Success = readPointerToBuffer(S.getContext(), Ptr, Buffer,
+ /*ReturnOnUninit=*/false);
+ assert(Buffer.size() == BuffSize * 8);
+
+ HasIndeterminateBits = !Buffer.allInitialized();
+ std::memcpy(Buff, Buffer.data(), BuffSize);
+
+ if (BigEndian)
+ swapBytes(Buff, BuffSize);
+ return Success;
+}
diff --git a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.h b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.h
new file mode 100644
index 00000000000000..84ba784e95e235
--- /dev/null
+++ b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.h
@@ -0,0 +1,26 @@
+//===------------------ InterpBuiltinBitCast.h ------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_AST_INTERP_BUILITN_BIT_CAST_H
+#define LLVM_CLANG_AST_INTERP_BUILITN_BIT_CAST_H
+
+#include <cstddef>
+
+namespace clang {
+namespace interp {
+class Pointer;
+class InterpState;
+class CodePtr;
+
+bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+ std::byte *Buff, size_t BuffSize, bool &HasIndeterminateBits);
+
+} // namespace interp
+} // namespace clang
+
+#endif
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 4fa9b6d61d5ab9..04166bbf3fddaa 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -836,3 +836,9 @@ def CheckNewTypeMismatchArray : Opcode {
}
def IsConstantContext: Opcode;
+
+def BitCast : Opcode {
+ let Types = [FixedSizeIntegralTypeClass];
+ let Args = [ArgBool, ArgUint32, ArgFltSemantics];
+ let HasGroup = 1;
+}
diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt
index 6195a16c2c68db..038856248c1604 100644
--- a/clang/lib/AST/CMakeLists.txt
+++ b/clang/lib/AST/CMakeLists.txt
@@ -74,6 +74,7 @@ add_clang_library(clangAST
ByteCode/Function.cpp
ByteCode/FunctionPointer.cpp
ByteCode/InterpBuiltin.cpp
+ ByteCode/InterpBuiltinBitCast.cpp
ByteCode/Floating.cpp
ByteCode/EvaluationResult.cpp
ByteCode/DynamicAllocator.cpp
diff --git a/clang/test/AST/ByteCode/builtin-bit-cast.cpp b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
new file mode 100644
index 00000000000000..0cab0dcc9ad1d1
--- /dev/null
+++ b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
@@ -0,0 +1,348 @@
+// RUN: %clang_cc1 -verify=ref,both -std=c++2a -fsyntax-only %s
+// RUN: %clang_cc1 -verify=ref,both -std=c++2a -fsyntax-only -triple aarch64_be-linux-gnu %s
+// RUN: %clang_cc1 -verify=ref,both -std=c++2a -fsyntax-only -triple powerpc64le-unknown-unknown -mabi=ieeelongdouble %s
+// RUN: %clang_cc1 -verify=ref,both -std=c++2a -fsyntax-only -triple powerpc64-unknown-unknown -mabi=ieeelongdouble %s
+
+// RUN: %clang_cc1 -verify=expected,both -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter %s
+// RUN: %clang_cc1 -verify=expected,both -std=c++2a -fsyntax-only -triple aarch64_be-linux-gnu -fexperimental-new-constant-interpreter %s
+// RUN: %clang_cc1 -verify=expected,both -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter -triple powerpc64le-unknown-unknown -mabi=ieeelongdouble %s
+// RUN: %clang_cc1 -verify=expected,both -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter -triple powerpc64-unknown-unknown -mabi=ieeelongdouble %s
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+# define LITTLE_END 1
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+# define LITTLE_END 0
+#else
+# error "huh?"
+#endif
+
+typedef decltype(nullptr) nullptr_t;
+typedef __INTPTR_TYPE__ intptr_t;
+
+static_assert(sizeof(int) == 4);
+static_assert(sizeof(long long) == 8);
+
+template <class To, class From>
+constexpr To bit_cast(const From &from) {
+ static_assert(sizeof(To) == sizeof(From));
+ return __builtin_bit_cast(To, from);
+}
+
+template <class Intermediate, class Init>
+constexpr bool check_round_trip(const Init &init) {
+ return bit_cast<Init>(bit_cast<Intermediate>(init)) == init;
+}
+
+template <class Intermediate, class Init>
+constexpr Init round_trip(const Init &init) {
+ return bit_cast<Init>(bit_cast<Intermediate>(init));
+}
+
+namespace std {
+enum byte : unsigned char {};
+} // namespace std
+
+using uint8_t = unsigned char;
+
+template<int N>
+struct bytes {
+ using size_t = unsigned int;
+ unsigned char d[N];
+
+ constexpr unsigned char &operator[](size_t index) {
+ if (index < N)
+ return d[index];
+ }
+};
+
+
+template <int N, typename T = unsigned char, int Pad = 0>
+struct bits {
+ T : Pad;
+ T bits : N;
+
+ constexpr bool operator==(const T& rhs) const {
+ return bits == rhs;
+ }
+};
+
+template <int N, typename T, int P>
+constexpr bool operator==(const struct bits<N, T, P>& lhs, const struct bits<N, T, P>& rhs) {
+ return lhs.bits == rhs.bits;
+}
+
+
+namespace simple {
+ constexpr int A = __builtin_bit_cast(int, 10);
+ static_assert(A == 10);
+
+ struct Bytes {
+ char a, b, c, d;
+ };
+ constexpr unsigned B = __builtin_bit_cast(unsigned, Bytes{10, 12, 13, 14});
+ static_assert(B == (LITTLE_END ? 235736074 : 168561934));
+
+
+ constexpr unsigned C = __builtin_bit_cast(unsigned, (_BitInt(32))12);
+ static_assert(C == 12);
+
+ struct BitInts {
+ _BitInt(16) a;
+ _BitInt(16) b;
+ };
+ constexpr unsigned D = __builtin_bit_cast(unsigned, BitInts{12, 13});
+ static_assert(D == (LITTLE_END ? 851980 : 786445));
+
+
+
+ static_assert(__builtin_bit_cast(char, true) == 1);
+
+ static_assert(check_round_trip<unsigned>((int)-1));
+ static_assert(check_round_trip<unsigned>((int)0x12345678));
+ static_assert(check_round_trip<unsigned>((int)0x87654321));
+ static_assert(check_round_trip<unsigned>((int)0x0C05FEFE));
+ // static_assert(round_trip<float>((int)0x0C05FEFE));
+}
+
+namespace bitint {
+ constexpr _BitInt(sizeof(int) * 8) BI = ~0;
+ constexpr unsigned int I = __builtin_bit_cast(unsigned int, BI);
+ static_assert(I == ~0u, "");
+
+ constexpr _BitInt(sizeof(int) * 8) IB = __builtin_bit_cast(_BitInt(sizeof(int) * 8), I); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{constexpr bit cast involving type '_BitInt(32)' is not yet supported}} \
+ // ref-note {{declared here}}
+ static_assert(IB == ~0u, ""); // ref-error {{not an integral constant expression}} \
+ // ref-note {{initializer of 'IB' is not a constant expression}}
+}
+
+namespace BitFields {
+ struct BitFields {
+ unsigned a : 2;
+ unsigned b : 30;
+ };
+
+ constexpr unsigned A = __builtin_bit_cast(unsigned, BitFields{3, 16}); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{not yet supported}} \
+ // ref-note {{declared here}}
+ static_assert(A == (LITTLE_END ? 67 : 50348032)); // ref-error {{not an integral constant expression}} \
+ // ref-note {{initializer of 'A'}}
+
+
+ void bitfield_indeterminate() {
+ struct BF { unsigned char z : 2; };
+ enum byte : unsigned char {};
+
+ constexpr BF bf = {0x3};
+ /// Requires bitcasts to composite types.
+ // static_assert(bit_cast<bits<2>>(bf).bits == bf.z);
+ // static_assert(bit_cast<unsigned char>(bf));
+
+#if 0
+ // static_assert(__builtin_bit_cast(byte, bf));
+
+ struct M {
+ // expected-note at +1 {{subobject declared here}}
+ unsigned char mem[sizeof(BF)];
+ };
+ // expected-error at +2 {{initialized by a constant expression}}
+ // expected-note at +1 {{not initialized}}
+ constexpr M m = bit_cast<M>(bf);
+
+ constexpr auto f = []() constexpr {
+ // bits<24, unsigned int, LITTLE_END ? 0 : 8> B = {0xc0ffee};
+ constexpr struct { unsigned short b1; unsigned char b0; } B = {0xc0ff, 0xee};
+ return bit_cast<bytes<4>>(B);
+ };
+
+ static_assert(f()[0] + f()[1] + f()[2] == 0xc0 + 0xff + 0xee);
+ {
+ // expected-error at +2 {{initialized by a constant expression}}
+ // expected-note at +1 {{read of uninitialized object is not allowed in a constant expression}}
+ constexpr auto _bad = f()[3];
+ }
+
+ struct B {
+ unsigned short s0 : 8;
+ unsigned short s1 : 8;
+ std::byte b0 : 4;
+ std::byte b1 : 4;
+ std::byte b2 : 4;
+ };
+ constexpr auto g = [f]() constexpr {
+ return bit_cast<B>(f());
+ };
+ static_assert(g().s0 + g().s1 + g().b0 + g().b1 == 0xc0 + 0xff + 0xe + 0xe);
+ {
+ // expected-error at +2 {{initialized by a constant expression}}
+ // expected-note at +1 {{read of uninitialized object is not allowed in a constant expression}}
+ constexpr auto _bad = g().b2;
+ }
+#endif
+ }
+}
+
+struct int_splicer {
+ unsigned x;
+ unsigned y;
+
+ constexpr int_splicer() : x(1), y(2) {}
+ constexpr int_splicer(unsigned x, unsigned y) : x(x), y(y) {}
+
+ constexpr bool operator==(const int_splicer &other) const {
+ return other.x == x && other.y == y;
+ }
+};
+
+constexpr int_splicer splice(0x0C05FEFE, 0xCAFEBABE);
+
+#if 0
+static_assert(bit_cast<unsigned long long>(splice) == (LITTLE_END
+ ? 0xCAFEBABE0C05FEFE
+ : 0x0C05FEFECAFEBABE));
+
+constexpr int_splicer IS = bit_cast<int_splicer>(0xCAFEBABE0C05FEFE);
+static_assert(bit_cast<int_splicer>(0xCAFEBABE0C05FEFE).x == (LITTLE_END
+ ? 0x0C05FEFE
+ : 0xCAFEBABE));
+
+static_assert(round_trip<unsigned long long>(splice));
+static_assert(round_trip<long long>(splice));
+#endif
+
+
+
+/// ---------------------------------------------------------------------------
+/// From here on, it's things copied from test/SemaCXX/constexpr-builtin-bit.cast.cpp
+
+void test_int() {
+ static_assert(round_trip<unsigned>((int)-1));
+ static_assert(round_trip<unsigned>((int)0x12345678));
+ static_assert(round_trip<unsigned>((int)0x87654321));
+ static_assert(round_trip<unsigned>((int)0x0C05FEFE));
+}
+
+void test_array() {
+ constexpr unsigned char input[] = {0xCA, 0xFE, 0xBA, 0xBE};
+ constexpr unsigned expected = LITTLE_END ? 0xBEBAFECA : 0xCAFEBABE;
+ static_assert(bit_cast<unsigned>(input) == expected);
+}
+
+void test_record() {
+ struct int_splicer {
+ unsigned x;
+ unsigned y;
+
+ constexpr bool operator==(const int_splicer &other) const {
+ return other.x == x && other.y == y;
+ }
+ };
+
+ constexpr int_splicer splice{0x0C05FEFE, 0xCAFEBABE};
+
+ static_assert(bit_cast<unsigned long long>(splice) == (LITTLE_END
+ ? 0xCAFEBABE0C05FEFE
+ : 0x0C05FEFECAFEBABE));
+
+ /// FIXME: Bit casts to composite types.
+ // static_assert(bit_cast<int_splicer>(0xCAFEBABE0C05FEFE).x == (LITTLE_END
+ // ? 0x0C05FEFE
+ // : 0xCAFEBABE));
+
+ // static_assert(check_round_trip<unsigned long long>(splice));
+ // static_assert(check_round_trip<long long>(splice));
+
+ struct base2 {
+ };
+
+ struct base3 {
+ unsigned z;
+ };
+
+ struct bases : int_splicer, base2, base3 {
+ unsigned doublez;
+ };
+
+ struct tuple4 {
+ unsigned x, y, z, doublez;
+
+ bool operator==(tuple4 const &other) const = default;
+ constexpr bool operator==(bases const &other) const {
+ return x == other.x && y == other.y &&
+ z == other.z && doublez == other.doublez;
+ }
+ };
+ // constexpr bases b = {{1, 2}, {}, {3}, 4};
+ // constexpr tuple4 t4 = bit_cast<tuple4>(b);
+ // static_assert(t4 == tuple4{1, 2, 3, 4});
+ // static_assert(round_trip<tuple4>(b));
+
+ // constexpr auto b2 = bit_cast<bases>(t4);
+ // static_assert(t4 == b2);
+}
+
+void test_partially_initialized() {
+ struct pad {
+ signed char x;
+ int y;
+ };
+
+ struct no_pad {
+ signed char x;
+ signed char p1, p2, p3;
+ int y;
+ };
+
+ static_assert(sizeof(pad) == sizeof(no_pad));
+
+#if 0
+ constexpr pad pir{4, 4};
+ constexpr int piw = bit_cast<no_pad>(pir).x; // both-error {{constexpr variable 'piw' must be initialized by a constant expression}} \
+ // both-note {{in call to 'bit_cast<no_pad, pad>(pir)'}}
+
+
+ constexpr no_pad bad = bit_cast<no_pad>(pir); // both-error {{constexpr variable 'bad' must be initialized by a constant expression}} \
+ // both-note {{in call to 'bit_cast<no_pad, pad>(pir)'}}
+ // constexpr pad fine = bit_cast<pad>(no_pad{1, 2, 3, 4, 5});
+ // static_assert(fine.x == 1 && fine.y == 5);
+#endif
+}
+
+
+void bad_types() {
+#if 0
+ union X {
+ int x;
+ };
+
+ struct G {
+ int g;
+ };
+ // expected-error at +2 {{constexpr variable 'g' must be initialized by a constant expression}}
+ // expected-note at +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 at +2 {{constexpr variable 'x' must be initialized by a constant expression}}
+ // expected-note at +1 {{bit_cast to a union type is not allowed in a constant expression}}
+ constexpr X x = __builtin_bit_cast(X, G{0});
+#endif
+ struct has_pointer {
+ int *ptr; // both-note {{invalid type 'int *' is a member of 'has_pointer'}}
+ };
+
+ constexpr intptr_t ptr = __builtin_bit_cast(intptr_t, has_pointer{0}); // both-error {{constexpr variable 'ptr' must be initialized by a constant expression}} \
+ // both-note {{bit_cast from a pointer type is not allowed in a constant expression}}
+
+#if 0
+ // expected-error at +2 {{constexpr variable 'hptr' must be initialized by a constant expression}}
+ // expected-note at +1 {{bit_cast to a pointer type is not allowed in a constant expression}}
+ constexpr has_pointer hptr = __builtin_bit_cast(has_pointer, 0ul);
+#endif
+}
+
+void test_array_fill() {
+ constexpr unsigned char a[4] = {1, 2};
+ constexpr unsigned int i = bit_cast<unsigned int>(a);
+ static_assert(i == (LITTLE_END ? 0x00000201 : 0x01020000));
+}
+
+
More information about the cfe-commits
mailing list