[clang] [clang][bytecode] Start implementing __builtin_bit_cast (PR #112126)

Timm Baeder via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 31 06:34:44 PDT 2024


Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/112126 at github.com>


https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/112126

>From 3e028394eb4f3ce9796359e3122cbc86270e43f1 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 1/6] [clang][bytecode] Start implementing __builtin_bit_cast

---
 clang/lib/AST/ByteCode/Boolean.h              |  10 +
 clang/lib/AST/ByteCode/Compiler.cpp           |  63 +++
 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               |  31 ++
 clang/lib/AST/ByteCode/InterpBuiltin.cpp      |   1 +
 .../lib/AST/ByteCode/InterpBuiltinBitCast.cpp | 353 ++++++++++++++++
 clang/lib/AST/ByteCode/InterpBuiltinBitCast.h |  26 ++
 clang/lib/AST/ByteCode/Opcodes.td             |  10 +
 clang/lib/AST/CMakeLists.txt                  |   1 +
 clang/test/AST/ByteCode/builtin-bit-cast.cpp  | 392 ++++++++++++++++++
 14 files changed, 928 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..78d75e75c7531a 100644
--- a/clang/lib/AST/ByteCode/Boolean.h
+++ b/clang/lib/AST/ByteCode/Boolean.h
@@ -81,6 +81,16 @@ class Boolean final {
 
   Boolean truncate(unsigned TruncBits) const { return *this; }
 
+  static Boolean bitcastFromMemory(const std::byte *Buff, unsigned BitWidth) {
+    // Boolean width is currently always 8 for all supported targets. If this
+    // changes we need to get the bool width from the target info.
+    assert(BitWidth == 8);
+    bool Val = static_cast<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 59e09a44d747b9..75f790d17033c8 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:
@@ -6426,6 +6429,66 @@ bool Compiler<Emitter>::emitDummyPtr(const DeclTy &D, const Expr *E) {
       return this->emitDecayPtr(PT_Ptr, PT, E);
     return false;
   }
+  return true;
+}
+
+//  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 DiscardResult mode for bitcasts.");
+
+  if (ToType->isNullPtrType()) {
+    if (!this->discard(SubExpr))
+      return false;
+
+    return this->emitNullPtr(nullptr, E);
+  }
+
+  if (FromType->isNullPtrType() && ToT) {
+    if (!this->discard(SubExpr))
+      return false;
+
+    return visitZeroInitializer(*ToT, ToType, E);
+  }
+  assert(!ToType->isReferenceType());
+
+  // Get a pointer to the value-to-cast on the stack.
+  if (!this->visit(SubExpr))
+    return false;
+
+  if (!ToT || ToT == PT_Ptr) {
+    // Conversion to an array or record type.
+    assert(false && "Implement bitcast to pointers.");
+  }
+  assert(ToT);
+
+  const llvm::fltSemantics *TargetSemantics = nullptr;
+  if (ToT == PT_Float)
+    TargetSemantics = &Ctx.getFloatSemantics(ToType);
+
+  // Conversion to a primitive type. FromType can be another
+  // primitive type, or a record/array.
+  bool ToTypeIsUChar = (ToType->isSpecificBuiltinType(BuiltinType::UChar) ||
+                        ToType->isSpecificBuiltinType(BuiltinType::Char_U));
+  uint32_t ResultBitWidth = std::max(Ctx.getBitWidth(ToType), 8u);
+
+  if (!this->emitBitCast(*ToT, ToTypeIsUChar || ToType->isStdByteType(),
+                         ResultBitWidth, TargetSemantics, E))
+    return false;
+
+  if (DiscardResult)
+    return this->emitPop(*ToT, E);
 
   return true;
 }
diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index 5627d5071e810a..d1b624daba6b99 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -374,6 +374,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 be537d22d5af1b..b82ce5aba40909 100644
--- a/clang/lib/AST/ByteCode/Integral.h
+++ b/clang/lib/AST/ByteCode/Integral.h
@@ -154,6 +154,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 f8aeaaca398fe8..6a37d0a3ba4a51 100644
--- a/clang/lib/AST/ByteCode/IntegralAP.h
+++ b/clang/lib/AST/ByteCode/IntegralAP.h
@@ -171,6 +171,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 6e45cfb7e8a20c..772dec13a1f1e7 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1551,6 +1551,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 c95b18ef72c966..954df56eb22982 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) {
@@ -3033,6 +3036,34 @@ 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>();
+
+  if (!CheckLoad(S, OpPC, FromPtr))
+    return false;
+
+  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 bitcasting to a floating type");
+  } 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 b00d2a1768b6b7..cb5092b7a0815b 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -10,6 +10,7 @@
 #include "Compiler.h"
 #include "EvalEmitter.h"
 #include "Interp.h"
+#include "InterpBuiltinBitCast.h"
 #include "PrimType.h"
 #include "clang/AST/OSLog.h"
 #include "clang/AST/RecordLayout.h"
diff --git a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
new file mode 100644
index 00000000000000..dfa79c3d7f57d7
--- /dev/null
+++ b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
@@ -0,0 +1,353 @@
+//===-------------------- InterpBuiltinBitCast.cpp --------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#include "InterpBuiltinBitCast.h"
+#include "Boolean.h"
+#include "Context.h"
+#include "FixedPoint.h"
+#include "Floating.h"
+#include "Integral.h"
+#include "IntegralAP.h"
+#include "InterpState.h"
+#include "MemberPointer.h"
+#include "Pointer.h"
+#include "Record.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/RecordLayout.h"
+#include "clang/Basic/TargetInfo.h"
+#include "llvm/ADT/BitVector.h"
+#include <bitset>
+
+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 bool bitof(std::byte B, unsigned BitIndex) {
+  return (B & (std::byte{1} << BitIndex)) != std::byte{0};
+}
+
+static void swapBytes(std::byte *M, size_t N) {
+  for (size_t I = 0; I != (N / 2); ++I)
+    std::swap(M[I], M[N - 1 - I]);
+}
+
+/// Track what bits have been initialized to known values and which ones
+/// have indeterminate value.
+/// All offsets are in bits.
+struct BitcastBuffer {
+  llvm::BitVector Data;
+
+  BitcastBuffer() = default;
+
+  size_t size() const { return Data.size(); }
+
+  const std::byte *data() const {
+    unsigned NBytes = Data.size() / 8;
+    unsigned BitVectorWordSize = sizeof(uintptr_t);
+    bool FullWord = (NBytes % BitVectorWordSize == 0);
+
+    // llvm::BitVector uses 64-bit fields internally, so when we have
+    // fewer bytes than that, we need to compensate for that on
+    // big endian hosts.
+    unsigned DataPlus;
+    if (llvm::sys::IsBigEndianHost)
+      DataPlus = BitVectorWordSize - (NBytes % BitVectorWordSize);
+    else
+      DataPlus = 0;
+
+    return reinterpret_cast<const std::byte *>(Data.getData().data()) +
+           (FullWord ? 0 : DataPlus);
+  }
+
+  bool allInitialized() const {
+    // FIXME: Implement.
+    return true;
+  }
+
+  void pushData(const std::byte *data, size_t BitOffset, size_t BitWidth,
+                bool BigEndianTarget) {
+    Data.reserve(BitOffset + BitWidth);
+
+    bool OnlyFullBytes = BitWidth % 8 == 0;
+    unsigned NBytes = BitWidth / 8;
+
+    size_t BitsHandled = 0;
+    // Read all full bytes first
+    for (size_t I = 0; I != NBytes; ++I) {
+      std::byte B =
+          BigEndianTarget ? data[NBytes - OnlyFullBytes - I] : data[I];
+      for (unsigned X = 0; X != 8; ++X) {
+        Data.push_back(bitof(B, X));
+        ++BitsHandled;
+      }
+    }
+
+    if (BitsHandled == BitWidth)
+      return;
+
+    // Rest of the bits.
+    assert((BitWidth - BitsHandled) < 8);
+    std::byte B = BigEndianTarget ? data[0] : data[NBytes];
+    for (size_t I = 0, E = (BitWidth - BitsHandled); I != E; ++I) {
+      Data.push_back(bitof(B, I));
+      ++BitsHandled;
+    }
+
+    assert(BitsHandled == BitWidth);
+  }
+};
+
+/// We use this to recursively iterate over all fields and elemends of a pointer
+/// and extract relevant data for a bitcast.
+static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset,
+                          DataFunc F) {
+  const Descriptor *FieldDesc = P.getFieldDesc();
+  assert(FieldDesc);
+
+  // Primitives.
+  if (FieldDesc->isPrimitive())
+    return F(P, FieldDesc->getPrimType(), Offset);
+
+  // Primitive arrays.
+  if (FieldDesc->isPrimitiveArray()) {
+    bool BigEndianTarget = Ctx.getASTContext().getTargetInfo().isBigEndian();
+    QualType ElemType = FieldDesc->getElemQualType();
+    size_t ElemSizeInBits = Ctx.getASTContext().getTypeSize(ElemType);
+    PrimType ElemT = *Ctx.classify(ElemType);
+    bool Ok = true;
+    for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) {
+      unsigned Index = BigEndianTarget ? (FieldDesc->getNumElems() - 1 - I) : I;
+      Ok = Ok && F(P.atIndex(Index), ElemT, Offset);
+      Offset += ElemSizeInBits;
+    }
+    return Ok;
+  }
+
+  // Composite arrays.
+  if (FieldDesc->isCompositeArray()) {
+    bool BigEndianTarget = Ctx.getASTContext().getTargetInfo().isBigEndian();
+    QualType ElemType = FieldDesc->getElemQualType();
+    size_t ElemSizeInBits = Ctx.getASTContext().getTypeSize(ElemType);
+    for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) {
+      unsigned Index = BigEndianTarget ? (FieldDesc->getNumElems() - 1 - I) : I;
+      enumerateData(P.atIndex(Index).narrow(), Ctx, Offset, F);
+      Offset += ElemSizeInBits;
+    }
+    return true;
+  }
+
+  // Records.
+  if (FieldDesc->isRecord()) {
+    bool BigEndianTarget = Ctx.getASTContext().getTargetInfo().isBigEndian();
+    const Record *R = FieldDesc->ElemRecord;
+    const ASTRecordLayout &Layout =
+        Ctx.getASTContext().getASTRecordLayout(R->getDecl());
+    bool Ok = true;
+    for (const auto &B : R->bases()) {
+      Pointer Elem = P.atField(B.Offset);
+      CharUnits ByteOffset =
+          Layout.getBaseClassOffset(cast<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(BigEndianTarget ? (R->getNumFields() - 1 - I) : I);
+      Pointer Elem = P.atField(Fi->Offset);
+      size_t BitOffset =
+          Offset + Layout.getFieldOffset(Fi->Decl->getFieldIndex());
+      Ok = Ok && enumerateData(Elem, Ctx, BitOffset, F);
+    }
+    return Ok;
+  }
+
+  llvm_unreachable("Unhandled data type");
+}
+
+static bool enumeratePointerFields(const Pointer &P, const Context &Ctx,
+                                   DataFunc F) {
+  return enumerateData(P, Ctx, 0, F);
+}
+
+//  This function is constexpr if and only if To, From, and the types of
+//  all subobjects of To and From are types T such that...
+//  (3.1) - is_union_v<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 SwapData = (ASTCtx.getTargetInfo().isLittleEndian() !=
+                   llvm::sys::IsLittleEndianHost);
+  bool BigEndianTarget = ASTCtx.getTargetInfo().isBigEndian();
+
+  return enumeratePointerFields(
+      FromPtr, Ctx,
+      [&](const Pointer &P, PrimType T, size_t BitOffset) -> bool {
+        if (!P.isInitialized()) {
+          assert(false && "Implement uninitialized value tracking");
+          return ReturnOnUninit;
+        }
+
+        assert(P.isInitialized());
+        // nullptr_t is a PT_Ptr for us, but it's still not std::is_pointer_v.
+        if (T == PT_Ptr)
+          assert(false && "Implement casting to pointer types");
+
+        CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(P.getType());
+        unsigned BitWidth;
+        if (const FieldDecl *FD = P.getField(); FD && FD->isBitField())
+          BitWidth = FD->getBitWidthValue(ASTCtx);
+        else
+          BitWidth = ASTCtx.toBits(ObjectReprChars);
+
+        llvm::SmallVector<std::byte> Buff(ObjectReprChars.getQuantity());
+        BITCAST_TYPE_SWITCH_WITH_FLOAT(T, {
+          T Val = P.deref<T>();
+          Val.bitcastToMemory(Buff.data());
+        });
+        if (SwapData)
+          swapBytes(Buff.data(), ObjectReprChars.getQuantity());
+
+        if (BitWidth != (Buff.size() * 8) && BigEndianTarget) {
+          Buffer.pushData(Buff.data() + (Buff.size() - 1 - (BitWidth / 8)),
+                          BitOffset, BitWidth, BigEndianTarget);
+        } else {
+          Buffer.pushData(Buff.data(), BitOffset, BitWidth, BigEndianTarget);
+        }
+        return true;
+      });
+}
+
+bool clang::interp::DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+                              std::byte *Buff, size_t BuffSize,
+                              bool &HasIndeterminateBits) {
+  assert(Ptr.isLive());
+  assert(Ptr.isBlockPointer());
+  assert(Buff);
+
+  BitcastBuffer Buffer;
+  if (!CheckBitcastType(S, OpPC, Ptr.getType(), /*IsToType=*/false))
+    return false;
+
+  bool Success = readPointerToBuffer(S.getContext(), Ptr, Buffer,
+                                     /*ReturnOnUninit=*/false);
+  assert(Buffer.size() == BuffSize * 8);
+
+  HasIndeterminateBits = !Buffer.allInitialized();
+  std::memcpy(Buff, Buffer.data(), BuffSize);
+
+  return Success;
+}
diff --git a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.h b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.h
new file mode 100644
index 00000000000000..84ba784e95e235
--- /dev/null
+++ b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.h
@@ -0,0 +1,26 @@
+//===------------------ InterpBuiltinBitCast.h ------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_AST_INTERP_BUILITN_BIT_CAST_H
+#define LLVM_CLANG_AST_INTERP_BUILITN_BIT_CAST_H
+
+#include <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 9136e6b51660d0..480febd895a240 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -837,3 +837,13 @@ def CheckNewTypeMismatchArray : Opcode {
 
 def IsConstantContext: Opcode;
 def CheckAllocations : Opcode;
+
+def BitCastTypeClass : TypeClass {
+  let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, IntAP, IntAPS, Bool, Float];
+}
+
+def BitCast : Opcode {
+  let Types = [BitCastTypeClass];
+  let Args = [ArgBool, ArgUint32, ArgFltSemantics];
+  let HasGroup = 1;
+}
diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt
index 6195a16c2c68db..038856248c1604 100644
--- a/clang/lib/AST/CMakeLists.txt
+++ b/clang/lib/AST/CMakeLists.txt
@@ -74,6 +74,7 @@ add_clang_library(clangAST
   ByteCode/Function.cpp
   ByteCode/FunctionPointer.cpp
   ByteCode/InterpBuiltin.cpp
+  ByteCode/InterpBuiltinBitCast.cpp
   ByteCode/Floating.cpp
   ByteCode/EvaluationResult.cpp
   ByteCode/DynamicAllocator.cpp
diff --git a/clang/test/AST/ByteCode/builtin-bit-cast.cpp b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
new file mode 100644
index 00000000000000..729ae4933ced33
--- /dev/null
+++ b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
@@ -0,0 +1,392 @@
+// 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);
+
+  static_assert(__builtin_bit_cast(unsigned, 1.0F) == 1065353216);
+
+  struct Bytes {
+    char a, b, c, d;
+  };
+  constexpr unsigned B = __builtin_bit_cast(unsigned, Bytes{10, 12, 13, 14});
+  static_assert(B == (LITTLE_END ? 235736074 : 168561934));
+
+
+  constexpr unsigned C = __builtin_bit_cast(unsigned, (_BitInt(32))12);
+  static_assert(C == 12);
+
+  struct BitInts {
+    _BitInt(16) a;
+    _BitInt(16) b;
+  };
+  constexpr unsigned D = __builtin_bit_cast(unsigned, BitInts{12, 13});
+  static_assert(D == (LITTLE_END ? 851980 : 786445));
+
+
+
+  static_assert(__builtin_bit_cast(char, true) == 1);
+
+  static_assert(check_round_trip<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));
+
+
+  /// This works in GCC and in the bytecode interpreter, but the current interpreter
+  /// diagnoses it.
+  static_assert(__builtin_bit_cast(intptr_t, nullptr) == 0); // ref-error {{not an integral constant expression}} \
+                                                             // ref-note {{indeterminate value can only initialize an object}}
+}
+
+namespace Fail {
+  constexpr int a = 1/0; // both-error {{must be initialized by a constant expression}} \
+                         // both-note {{division by zero}} \
+                         // both-note {{declared here}}
+  constexpr int b = __builtin_bit_cast(int, a); // both-error {{must be initialized by a constant expression}} \
+                                                // both-note {{initializer of 'a' is not a constant expression}}
+}
+
+namespace bitint {
+  constexpr _BitInt(sizeof(int) * 8) BI = ~0;
+  constexpr unsigned int I = __builtin_bit_cast(unsigned int, BI);
+  static_assert(I == ~0u, "");
+
+  constexpr _BitInt(sizeof(int) * 8) IB = __builtin_bit_cast(_BitInt(sizeof(int) * 8), I); // ref-error {{must be initialized by a constant expression}} \
+                                                                                           // ref-note {{constexpr bit cast involving type '_BitInt(32)' is not yet supported}} \
+                                                                                           // ref-note {{declared here}}
+  static_assert(IB == ~0u, ""); // ref-error {{not an integral constant expression}} \
+                                // ref-note {{initializer of 'IB' is not a constant expression}}
+}
+
+namespace BitFields {
+  struct BitFields {
+    unsigned a : 2;
+    unsigned b : 30;
+  };
+
+  constexpr unsigned A = __builtin_bit_cast(unsigned, BitFields{3, 16}); // ref-error {{must be initialized by a constant expression}} \
+                                                                         // ref-note {{not yet supported}} \
+                                                                         // ref-note {{declared here}}
+  static_assert(A == (LITTLE_END ? 67 : 3221225488)); // ref-error {{not an integral constant expression}} \
+                                                      // ref-note {{initializer of 'A'}}
+
+
+  void bitfield_indeterminate() {
+    struct BF { unsigned char z : 2; };
+    enum byte : unsigned char {};
+
+    constexpr BF bf = {0x3};
+    /// Requires bitcasts to composite types.
+    // static_assert(bit_cast<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);
+
+  /// Same things but with a composite array.
+  struct US { unsigned char I; };
+  constexpr US input2[] = {{0xCA}, {0xFE}, {0xBA}, {0xBE}};
+  static_assert(bit_cast<unsigned>(input2) == expected);
+}
+
+void test_record() {
+  struct int_splicer {
+    unsigned x;
+    unsigned y;
+
+    constexpr bool operator==(const int_splicer &other) const {
+      return other.x == x && other.y == y;
+    }
+  };
+
+  constexpr int_splicer splice{0x0C05FEFE, 0xCAFEBABE};
+
+  static_assert(bit_cast<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));
+}
+
+struct vol_mem {
+  volatile int x;
+};
+
+// both-error at +2 {{constexpr variable 'run_vol_mem' must be initialized by a constant expression}}
+// both-note at +1 {{non-literal type 'vol_mem' cannot be used in a constant expression}}
+constexpr int run_vol_mem = __builtin_bit_cast(int, vol_mem{43});
+
+struct mem_ptr {
+  int vol_mem::*x; // both-note{{invalid type 'int vol_mem::*' is a member of 'mem_ptr'}}
+};
+
+// both-error at +2 {{constexpr variable 'run_mem_ptr' must be initialized by a constant expression}}
+// both-note at +1 {{bit_cast from a member pointer type is not allowed in a constant expression}}
+constexpr _BitInt(sizeof(mem_ptr) * 8) run_mem_ptr = __builtin_bit_cast(_BitInt(sizeof(mem_ptr) * 8), mem_ptr{nullptr});
+
+constexpr int global_int = 0;
+
+struct ref_mem {
+  const int &rm;
+};
+// both-error at +2 {{constexpr variable 'run_ref_mem' must be initialized by a constant expression}}
+// both-note at +1 {{bit_cast from a type with a reference member is not allowed in a constant expression}}
+constexpr intptr_t run_ref_mem = __builtin_bit_cast(intptr_t, ref_mem{global_int});

>From 1c9e99cbff30461b7095861da95de5c5c9caf8ac Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 30 Oct 2024 13:30:43 +0100
Subject: [PATCH 2/6] Add test for casting to nullptr_t

---
 clang/test/AST/ByteCode/builtin-bit-cast.cpp | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/clang/test/AST/ByteCode/builtin-bit-cast.cpp b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
index 729ae4933ced33..e4b46ac0fd7bbd 100644
--- a/clang/test/AST/ByteCode/builtin-bit-cast.cpp
+++ b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
@@ -120,6 +120,13 @@ namespace Fail {
                                                 // both-note {{initializer of 'a' is not a constant expression}}
 }
 
+namespace NullPtr {
+  constexpr nullptr_t N = __builtin_bit_cast(nullptr_t, 1UL);
+  static_assert(N == nullptr);
+  static_assert(__builtin_bit_cast(nullptr_t, (_BitInt(sizeof(void*) * 8))12) == __builtin_bit_cast(nullptr_t, (unsigned _BitInt(sizeof(void*) * 8))0));
+  static_assert(__builtin_bit_cast(nullptr_t, nullptr) == nullptr);
+}
+
 namespace bitint {
   constexpr _BitInt(sizeof(int) * 8) BI = ~0;
   constexpr unsigned int I = __builtin_bit_cast(unsigned int, BI);

>From f5be7bb554a8bfa550c8d485bef0059888b216e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 30 Oct 2024 14:05:15 +0100
Subject: [PATCH 3/6] Fix precommit CI failure

---
 clang/test/AST/ByteCode/builtin-bit-cast.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/test/AST/ByteCode/builtin-bit-cast.cpp b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
index e4b46ac0fd7bbd..c461d8b597955f 100644
--- a/clang/test/AST/ByteCode/builtin-bit-cast.cpp
+++ b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
@@ -121,7 +121,7 @@ namespace Fail {
 }
 
 namespace NullPtr {
-  constexpr nullptr_t N = __builtin_bit_cast(nullptr_t, 1UL);
+  constexpr nullptr_t N = __builtin_bit_cast(nullptr_t, (intptr_t)1u);
   static_assert(N == nullptr);
   static_assert(__builtin_bit_cast(nullptr_t, (_BitInt(sizeof(void*) * 8))12) == __builtin_bit_cast(nullptr_t, (unsigned _BitInt(sizeof(void*) * 8))0));
   static_assert(__builtin_bit_cast(nullptr_t, nullptr) == nullptr);

>From 5612432ec1b201ee0806b56c807f52c0eba0af8a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 30 Oct 2024 15:39:11 +0100
Subject: [PATCH 4/6] Address review comments

 1) add the static_assert
 2) Add more tests
 3) for 2), Fix iterating over record bases/fields. need to
    invert the field/base order as well.
---
 clang/lib/AST/ByteCode/Integral.h             |  1 +
 .../lib/AST/ByteCode/InterpBuiltinBitCast.cpp | 42 ++++++++++++-------
 clang/test/AST/ByteCode/builtin-bit-cast.cpp  | 38 +++++++++++++++++
 3 files changed, 67 insertions(+), 14 deletions(-)

diff --git a/clang/lib/AST/ByteCode/Integral.h b/clang/lib/AST/ByteCode/Integral.h
index b82ce5aba40909..ca3674263aef4f 100644
--- a/clang/lib/AST/ByteCode/Integral.h
+++ b/clang/lib/AST/ByteCode/Integral.h
@@ -70,6 +70,7 @@ template <unsigned Bits, bool Signed> class Integral final {
   // The primitive representing the integral.
   using ReprT = typename Repr<Bits, Signed>::Type;
   ReprT V;
+  static_assert(std::is_trivially_copyable_v<ReprT>);
 
   /// Primitive representing limits.
   static const auto Min = std::numeric_limits<ReprT>::min();
diff --git a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
index dfa79c3d7f57d7..fde2c6d9b11ac8 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
@@ -191,22 +191,36 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset,
     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(BigEndianTarget ? (R->getNumFields() - 1 - I) : I);
-      Pointer Elem = P.atField(Fi->Offset);
-      size_t BitOffset =
-          Offset + Layout.getFieldOffset(Fi->Decl->getFieldIndex());
-      Ok = Ok && enumerateData(Elem, Ctx, BitOffset, F);
+    auto enumerateFields = [&]() -> void {
+      for (unsigned I = 0, N = R->getNumFields(); I != N; ++I) {
+        const Record::Field *Fi =
+            R->getField(BigEndianTarget ? (N - 1 - I) : I);
+        Pointer Elem = P.atField(Fi->Offset);
+        size_t BitOffset =
+            Offset + Layout.getFieldOffset(Fi->Decl->getFieldIndex());
+        Ok = Ok && enumerateData(Elem, Ctx, BitOffset, F);
+      }
+    };
+    auto enumerateBases = [&]() -> void {
+      for (unsigned I = 0, N = R->getNumBases(); I != N; ++I) {
+        const Record::Base *B = R->getBase(BigEndianTarget ? (N - 1 - I) : I);
+        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);
+      }
+    };
+
+    if (BigEndianTarget) {
+      enumerateFields();
+      enumerateBases();
+    } else {
+      enumerateBases();
+      enumerateFields();
     }
+
     return Ok;
   }
 
diff --git a/clang/test/AST/ByteCode/builtin-bit-cast.cpp b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
index c461d8b597955f..30060c1aad8246 100644
--- a/clang/test/AST/ByteCode/builtin-bit-cast.cpp
+++ b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
@@ -205,6 +205,44 @@ namespace BitFields {
   }
 }
 
+namespace Classes {
+  class A {
+  public:
+    char a[2];
+  };
+  class B : public A {
+  public:
+    char b[2];
+  };
+  static_assert(__builtin_bit_cast(int, B{{0,  0},{0,  0}}) == 0);
+  static_assert(__builtin_bit_cast(int, B{{13, 0},{0,  0}}) == (LITTLE_END ? 13 : 218103808));
+  static_assert(__builtin_bit_cast(int, B{{13, 7},{12, 20}}) == (LITTLE_END ? 336332557 : 218565652));
+
+  class Ref {
+  public:
+    const int &a;
+    constexpr Ref(const int &a) : a(a) {}
+  };
+  constexpr int I = 12;
+
+  typedef __INTPTR_TYPE__ intptr_t;
+  static_assert(__builtin_bit_cast(intptr_t, Ref{I}) == 0); // both-error {{not an integral constant expression}} \
+                                                            // both-note {{bit_cast from a type with a reference member is not allowed in a constant expression}}
+
+  class C : public A {
+    public:
+    constexpr C() : A{1,2} {}
+    virtual constexpr int get() {
+      return 4;
+    }
+  };
+  static_assert(__builtin_bit_cast(_BitInt(sizeof(C) * 8), C()) == 0); // both-error {{source type must be trivially copyable}}
+
+
+  class D : virtual A {};
+  static_assert(__builtin_bit_cast(_BitInt(sizeof(D) * 8), D()) == 0); // both-error {{source type must be trivially copyable}}
+}
+
 struct int_splicer {
   unsigned x;
   unsigned y;

>From 2839e0b36e041dc01ba59f56ba45b671712a64b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 30 Oct 2024 15:55:30 +0100
Subject: [PATCH 5/6] Add a test with a class with 2 bases

---
 clang/test/AST/ByteCode/builtin-bit-cast.cpp | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/clang/test/AST/ByteCode/builtin-bit-cast.cpp b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
index 30060c1aad8246..8b8213bc790950 100644
--- a/clang/test/AST/ByteCode/builtin-bit-cast.cpp
+++ b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
@@ -241,6 +241,18 @@ namespace Classes {
 
   class D : virtual A {};
   static_assert(__builtin_bit_cast(_BitInt(sizeof(D) * 8), D()) == 0); // both-error {{source type must be trivially copyable}}
+
+  class F {
+  public:
+    char f[2];
+  };
+
+  class E : public A, public F {
+  public:
+    constexpr E() : A{1,2}, F{3,4}, e{5,6,7,8} {}
+    char e[4];
+  };
+  static_assert(__builtin_bit_cast(long long, E()) == (LITTLE_END ? 578437695752307201 : 72623859790382856));
 }
 
 struct int_splicer {

>From 12280e6d2cb096e59d3de0c94760cc4b72370e5c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Thu, 31 Oct 2024 14:34:20 +0100
Subject: [PATCH 6/6] Add  a union -> int test case

---
 clang/test/AST/ByteCode/builtin-bit-cast.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/clang/test/AST/ByteCode/builtin-bit-cast.cpp b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
index 8b8213bc790950..58cf486833412f 100644
--- a/clang/test/AST/ByteCode/builtin-bit-cast.cpp
+++ b/clang/test/AST/ByteCode/builtin-bit-cast.cpp
@@ -388,10 +388,12 @@ void test_partially_initialized() {
 
 
 void bad_types() {
-#if 0
   union X {
     int x;
   };
+  static_assert(__builtin_bit_cast(int, X{0}) == 0); // both-error {{not an integral constant expression}} \
+                                                     // both-note {{bit_cast from a union type is not allowed in a constant expression}}
+#if 0
 
   struct G {
     int g;



More information about the cfe-commits mailing list