[clang] [clang][Interp] Implement __builtin_bit_cast (PR #68288)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 5 02:45:54 PDT 2023
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>
Message-ID:
In-Reply-To: <llvm/llvm-project/pull/68288/clang at github.com>
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/68288
>From e208c2fc7932d80d441d90bfa0fd949b8910a4c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Tue, 11 Jul 2023 13:45:04 +0200
Subject: [PATCH 1/2] Bitcasts
---
clang/lib/AST/CMakeLists.txt | 1 +
clang/lib/AST/Interp/Boolean.h | 15 +-
clang/lib/AST/Interp/ByteCodeExprGen.cpp | 71 +++
clang/lib/AST/Interp/ByteCodeExprGen.h | 1 +
clang/lib/AST/Interp/Floating.h | 11 +
clang/lib/AST/Interp/Integral.h | 19 +-
clang/lib/AST/Interp/Interp.cpp | 17 +
clang/lib/AST/Interp/Interp.h | 66 ++
clang/lib/AST/Interp/InterpBitcast.cpp | 482 +++++++++++++++
clang/lib/AST/Interp/Opcodes.td | 17 +
clang/lib/AST/Interp/PrimType.h | 4 +
clang/test/AST/Interp/builtin-bit-cast.cpp | 683 +++++++++++++++++++++
clang/test/AST/Interp/literals.cpp | 5 +
13 files changed, 1389 insertions(+), 3 deletions(-)
create mode 100644 clang/lib/AST/Interp/InterpBitcast.cpp
create mode 100644 clang/test/AST/Interp/builtin-bit-cast.cpp
diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt
index 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 ASTRecordLayout &Layout =
+ Ctx.getASTContext().getASTRecordLayout(R->getDecl());
+ for (const auto &B : R->bases()) {
+ Pointer Elem = P.atField(B.Offset);
+ size_t ByteOffset =
+ Offset +
+ Layout.getBaseClassOffset(cast<CXXRecordDecl>(B.Decl)).getQuantity();
+ if (!enumerateData(Elem, Ctx, ByteOffset, F))
+ return false;
+ }
+ // TODO: Virtual bases?
+
+ for (unsigned I = 0; I != R->getNumFields(); ++I) {
+ Pointer Elem = P.atField(R->getField(I)->Offset);
+ CharUnits FieldOffset =
+ Ctx.getASTContext().toCharUnitsFromBits(Layout.getFieldOffset(I));
+ size_t ByteOffset = Offset + FieldOffset.getQuantity();
+ if (!enumerateData(Elem, Ctx, ByteOffset, F))
+ return false;
+ }
+ return true;
+ }
+
+ llvm_unreachable("Unhandled data type");
+}
+
+static bool enumeratePointerFields(const Pointer &P, const Context &Ctx,
+ DataFunc F) {
+ return enumerateData(P, Ctx, 0, F);
+}
+
+// This function is constexpr if and only if To, From, and the types of
+// all subobjects of To and From are types T such that...
+// (3.1) - is_union_v<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.getCtx().getBaseElementType(T), IsToType))
+ return false;
+
+ return true;
+}
+
+/// Bitcast all fields from \p P into \p Buff.
+/// This is used for bitcasting TO a single primitive value.
+bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff,
+ size_t BuffSize, unsigned &IndeterminateBits) {
+ assert(P.isLive());
+ assert(Buff);
+ assert(BuffSize > 0);
+
+ BitcastBuffer F(Buff, BuffSize, S.getCtx().getTargetInfo().isBigEndian());
+
+ if (!CheckBitcastType(S, OpPC, P.getType(), /*IsToType=*/false))
+ return false;
+
+ const Context &Ctx = S.getContext();
+ const ASTContext &ASTCtx = Ctx.getASTContext();
+ uint64_t PointerSize =
+ ASTCtx
+ .toCharUnitsFromBits(
+ ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default))
+ .getQuantity();
+
+ bool Success = enumeratePointerFields(
+ P, S.getContext(),
+ [&](const Pointer &Ptr, PrimType T, size_t ByteOffset) -> bool {
+ if (!Ptr.isInitialized())
+ return false;
+ if (T == PT_Ptr) {
+ assert(Ptr.getType()->isNullPtrType());
+ std::byte *M = F.getBytes(ByteOffset, PointerSize);
+ std::memset(M, 0, PointerSize);
+ return true;
+ }
+
+ BITCAST_TYPE_SWITCH_WITH_FLOAT(T, {
+ T Val = Ptr.deref<T>();
+ unsigned ObjectReprBytes =
+ ASTCtx.getTypeSizeInChars(Ptr.getType()).getQuantity();
+ std::byte *M = F.getBytes(ByteOffset, ObjectReprBytes);
+ Val.bitcastToMemory(M);
+ });
+ return true;
+ });
+
+ IndeterminateBits = F.IndeterminateBits;
+ return Success;
+}
+
+// This function is constexpr if and only if To, From, and the types of
+// all subobjects of To and From are types T such that...
+// (3.1) - is_union_v<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
+bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr,
+ CodePtr OpPC) {
+ assert(P.isLive());
+ assert(DestPtr.isLive());
+
+ QualType FromType = P.getType();
+ QualType ToType = DestPtr.getType();
+
+ if (!CheckBitcastType(S, OpPC, FromType, /*IsToType=*/false))
+ return false;
+
+ if (!CheckBitcastType(S, OpPC, ToType, /*IsToType=*/true))
+ return false;
+
+ const Context &Ctx = S.getContext();
+ const ASTContext &ASTCtx = Ctx.getASTContext();
+ uint64_t PointerSize =
+ ASTCtx
+ .toCharUnitsFromBits(
+ ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default))
+ .getQuantity();
+ bool BigEndian = ASTCtx.getTargetInfo().isBigEndian();
+
+ ByteTracker Bytes;
+ enumeratePointerFields(
+ P, S.getContext(),
+ [&](const Pointer &P, PrimType T, size_t ByteOffset) -> bool {
+ bool PtrInitialized = P.isInitialized();
+ if (!PtrInitialized) {
+ Bytes.markUninitializedUntil(ByteOffset + primSize(T));
+ return true;
+ }
+
+ assert(P.isInitialized());
+ // nullptr_t is a PT_Ptr for us, but it's still not std::is_pointer_v.
+ if (T == PT_Ptr) {
+ assert(P.getType()->isNullPtrType());
+ std::byte *M = Bytes.getWritableBytes(ByteOffset, PointerSize,
+ /*InitValue=*/true);
+ std::memset(M, 0, PointerSize);
+ return true;
+ }
+ BITCAST_TYPE_SWITCH_WITH_FLOAT(T, {
+ T Val = P.deref<T>();
+ unsigned ObjectReprBytes =
+ ASTCtx.getTypeSizeInChars(P.getType()).getQuantity();
+ unsigned ValueReprBytes = Val.valueReprBytes(ASTCtx);
+ assert(ObjectReprBytes >= ValueReprBytes);
+
+ std::byte *Dest = Bytes.getWritableBytes(ByteOffset, ValueReprBytes,
+ PtrInitialized);
+ Val.bitcastToMemory(Dest);
+ Bytes.zeroUntil(ByteOffset + ObjectReprBytes);
+
+ if (BigEndian)
+ swapBytes(Dest, ValueReprBytes);
+ });
+ return true;
+ });
+
+ bool Success = enumeratePointerFields(
+ DestPtr, S.getContext(),
+ [&](const Pointer &P, PrimType T, size_t ByteOffset) -> bool {
+ if (T == PT_Float) {
+ const QualType FloatType = P.getFieldDesc()->getType();
+ const auto &Sem = ASTCtx.getFloatTypeSemantics(FloatType);
+ size_t ValueReprBytes =
+ ASTCtx.toCharUnitsFromBits(APFloat::semanticsSizeInBits(Sem))
+ .getQuantity();
+
+ std::byte *M = Bytes.getBytes(ByteOffset);
+
+ if (BigEndian)
+ swapBytes(M, ValueReprBytes);
+ P.deref<Floating>() = Floating::bitcastFromMemory(M, Sem);
+ P.initialize();
+ return true;
+ }
+ if (T == PT_Ptr) {
+ assert(P.getType()->isNullPtrType());
+ // Just need to write out a nullptr.
+ P.deref<Pointer>() = Pointer();
+ P.initialize();
+ return true;
+ }
+
+ BITCAST_TYPE_SWITCH(T, {
+ T &Val = P.deref<T>();
+
+ size_t ValueReprBytes = T::valueReprBytes(ASTCtx);
+ // Check if any of the bits we're about to read are uninitialized.
+ bool HasIndeterminateBytes =
+ !Bytes.allInitialized(ByteOffset, ValueReprBytes);
+
+ if (HasIndeterminateBytes) {
+ // Always an error, unless the type of the field we're reading is
+ // either unsigned char or std::byte.
+ bool TargetIsUCharOrBytes =
+ (ValueReprBytes == 1 &&
+ (P.getType()->isSpecificBuiltinType(BuiltinType::UChar) ||
+ P.getType()->isSpecificBuiltinType(BuiltinType::Char_U) ||
+ P.getType()->isStdByteType()));
+
+ if (!TargetIsUCharOrBytes) {
+ const Expr *E = S.Current->getExpr(OpPC);
+ QualType ExprType = P.getType();
+ S.FFDiag(E, diag::note_constexpr_bit_cast_indet_dest)
+ << ExprType << S.getLangOpts().CharIsSigned
+ << E->getSourceRange();
+ return false;
+ }
+ }
+
+ std::byte *M = Bytes.getBytes(ByteOffset);
+ if (BigEndian)
+ swapBytes(M, ValueReprBytes);
+ Val = T::bitcastFromMemory(M);
+
+ if (!HasIndeterminateBytes)
+ P.initialize();
+ });
+ return true;
+ });
+
+ return Success;
+}
+} // namespace interp
+} // namespace clang
diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td
index 50b6c0ac154de30..ad1ad1bafea2eb6 100644
--- a/clang/lib/AST/Interp/Opcodes.td
+++ b/clang/lib/AST/Interp/Opcodes.td
@@ -572,6 +572,23 @@ def Cast: Opcode {
let HasGroup = 1;
}
+def BitCastTypeClass : TypeClass {
+ let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, Bool];
+}
+
+def BitCast : Opcode {
+ let Types = [BitCastTypeClass];
+ let HasGroup = 1;
+ let Args = [ArgBool];
+}
+
+def BitCastPtr : Opcode;
+
+def BitCastFP : Opcode {
+ let Types = [];
+ let Args = [ArgFltSemantics, ArgUint32];
+}
+
def CastFP : Opcode {
let Types = [];
let Args = [ArgFltSemantics, ArgRoundingMode];
diff --git a/clang/lib/AST/Interp/PrimType.h b/clang/lib/AST/Interp/PrimType.h
index 8c5e87f37be1867..66f4b747a13db4c 100644
--- a/clang/lib/AST/Interp/PrimType.h
+++ b/clang/lib/AST/Interp/PrimType.h
@@ -87,6 +87,10 @@ template <> struct PrimConv<PT_FnPtr> {
/// Returns the size of a primitive type in bytes.
size_t primSize(PrimType Type);
+template <PrimType PrimT> constexpr size_t primSize() {
+ return sizeof(typename PrimConv<PrimT>::T);
+}
+
/// Aligns a size to the pointer alignment.
constexpr size_t align(size_t Size) {
return ((Size + alignof(void *) - 1) / alignof(void *)) * alignof(void *);
diff --git a/clang/test/AST/Interp/builtin-bit-cast.cpp b/clang/test/AST/Interp/builtin-bit-cast.cpp
new file mode 100644
index 000000000000000..c5b8ca12570a138
--- /dev/null
+++ b/clang/test/AST/Interp/builtin-bit-cast.cpp
@@ -0,0 +1,683 @@
+// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter %s
+// RUN: %clang_cc1 -verify=ref -std=c++2a -fsyntax-only %s
+// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -triple aarch64_be-linux-gnu -fexperimental-new-constant-interpreter %s
+// RUN: %clang_cc1 -verify=ref -std=c++2a -fsyntax-only -triple aarch64_be-linux-gnu %s
+// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter -triple powerpc64le-unknown-unknown -mabi=ieeelongdouble %s
+// RUN: %clang_cc1 -verify=ref -std=c++2a -fsyntax-only -triple powerpc64le-unknown-unknown -mabi=ieeelongdouble %s
+// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter -triple powerpc64-unknown-unknown -mabi=ieeelongdouble %s
+// RUN: %clang_cc1 -verify=ref -std=c++2a -fsyntax-only -triple powerpc64-unknown-unknown -mabi=ieeelongdouble %s
+
+/// FIXME: This is a version of
+/// clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp with the currently
+/// supported subset of operations. They should *all* be supported though.
+
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+# define LITTLE_END 1
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+# define LITTLE_END 0
+#else
+# error "huh?"
+#endif
+
+typedef decltype(nullptr) nullptr_t;
+
+static_assert(sizeof(int) == 4);
+static_assert(sizeof(long long) == 8);
+
+template <class To, class From>
+constexpr To bit_cast(const From &from) {
+ static_assert(sizeof(To) == sizeof(From));
+ return __builtin_bit_cast(To, from); // ref-note 2{{indeterminate value can only initialize}} \
+ // expected-note 2{{indeterminate value can only initialize}}
+}
+
+template <class Intermediate, class Init>
+constexpr bool round_trip(const Init &init) {
+ return bit_cast<Init>(bit_cast<Intermediate>(init)) == init;
+}
+
+/// We can ignore it.
+constexpr int foo() {
+ (void)__builtin_bit_cast(int, 3);
+ return 1;
+}
+static_assert(foo() == 1, "");
+
+namespace Ints {
+ static_assert(round_trip<unsigned>((int)-1));
+ static_assert(round_trip<unsigned>((int)0x12345678));
+ static_assert(round_trip<unsigned>((int)0x87654321));
+ static_assert(round_trip<unsigned>((int)0x0C05FEFE));
+ static_assert(round_trip<float>((int)0x0C05FEFE));
+}
+
+namespace FloatToDouble {
+ constexpr float F1[] = {1.0f, 2.0f};
+ constexpr double D1 = __builtin_bit_cast(double, F1);
+ static_assert(D1 > 0);
+}
+
+namespace Arrays {
+ constexpr unsigned char input[] = {0xCA, 0xFE, 0xBA, 0xBE};
+ constexpr unsigned expected = LITTLE_END ? 0xBEBAFECA : 0xCAFEBABE;
+ static_assert(bit_cast<unsigned>(input) == expected);
+
+ constexpr short S[] = {10, 20};
+ constexpr int I = __builtin_bit_cast(int, S);
+ static_assert(I == (LITTLE_END ? 1310730 : 655380));
+}
+
+struct int_splicer {
+ unsigned x;
+ unsigned y;
+
+ constexpr int_splicer() : x(1), y(2) {}
+ constexpr int_splicer(unsigned x, unsigned y) : x(x), y(y) {}
+
+ constexpr bool operator==(const int_splicer &other) const {
+ return other.x == x && other.y == y;
+ }
+};
+
+constexpr int_splicer splice(0x0C05FEFE, 0xCAFEBABE);
+
+static_assert(bit_cast<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));
+
+struct base2 {
+};
+
+struct base3 {
+ unsigned z;
+ constexpr base3() : z(3) {}
+};
+
+struct bases : int_splicer, base2, base3 {
+ unsigned doublez;
+ constexpr bases() : doublez(4) {}
+};
+
+struct tuple4 {
+ unsigned x, y, z, doublez;
+
+ constexpr bool operator==(tuple4 const &other) const {
+ return x == other.x && y == other.y &&
+ z == other.z && doublez == other.doublez;
+ }
+};
+constexpr bases b;// = {{1, 2}, {}, {3}, 4};
+constexpr tuple4 t4 = bit_cast<tuple4>(b);
+
+// Regardless of endianness, this should hold:
+static_assert(t4.x == 1);
+static_assert(t4.y == 2);
+static_assert(t4.z == 3);
+static_assert(t4.doublez == 4);
+static_assert(t4 == tuple4{1, 2, 3, 4});
+static_assert(round_trip<tuple4>(b));
+
+namespace WithBases {
+ struct Base {
+ char A[3] = {1,2,3};
+ };
+
+ struct A : Base {
+ char B = 12;
+ };
+
+ constexpr A a;
+ constexpr unsigned I = __builtin_bit_cast(unsigned, a);
+ static_assert(I == (LITTLE_END ? 201523713 : 16909068));
+};
+
+
+
+void test_array() {
+ constexpr unsigned char input[] = {0xCA, 0xFE, 0xBA, 0xBE};
+ constexpr unsigned expected = LITTLE_END ? 0xBEBAFECA : 0xCAFEBABE;
+ static_assert(bit_cast<unsigned>(input) == expected);
+}
+
+
+namespace 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));
+}
+
+namespace Another {
+ constexpr char C[] = {1,2,3,4};
+ struct F{ short a; short b; };
+ constexpr F f = __builtin_bit_cast(F, C);
+
+#if LITTLE_END
+ static_assert(f.a == 513);
+ static_assert(f.b == 1027);
+#else
+ static_assert(f.a == 258);
+ static_assert(f.b == 772);
+#endif
+}
+
+
+namespace array_members {
+ struct S {
+ int ar[3];
+
+ constexpr bool operator==(const S &rhs) {
+ return ar[0] == rhs.ar[0] && ar[1] == rhs.ar[1] && ar[2] == rhs.ar[2];
+ }
+ };
+
+ struct G {
+ int a, b, c;
+
+ constexpr bool operator==(const G &rhs) {
+ return a == rhs.a && b == rhs.b && c == rhs.c;
+ }
+ };
+
+ constexpr S s{{1, 2, 3}};
+ constexpr G g = bit_cast<G>(s);
+ static_assert(g.a == 1 && g.b == 2 && g.c == 3);
+
+ static_assert(round_trip<G>(s));
+ static_assert(round_trip<S>(g));
+}
+
+namespace CompositeArrays {
+ struct F {
+ int a;
+ int b;
+ };
+
+ static_assert(sizeof(long) == 2 * sizeof(int));
+
+ constexpr F ff[] = {{1,2}};
+ constexpr long L = __builtin_bit_cast(long, ff);
+
+#if LITTLE_END
+ static_assert(L == 8589934593);
+#else
+ static_assert(L == 4294967298);
+#endif
+}
+
+
+
+#ifdef __CHAR_UNSIGNED__
+// ref-note at +5 {{indeterminate value can only initialize an object of type 'unsigned char', 'char', or 'std::byte'; 'unsigned long' is invalid}}
+#else
+// ref-note at +3 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'unsigned long' is invalid}}
+#endif
+// ref-error at +1 {{constexpr variable 'test_from_nullptr' must be initialized by a constant expression}}
+constexpr unsigned long test_from_nullptr = __builtin_bit_cast(unsigned long, nullptr);
+
+constexpr int test_from_nullptr_pass = (__builtin_bit_cast(unsigned char[8], nullptr), 0);
+
+constexpr int test_to_nullptr() {
+ nullptr_t npt = __builtin_bit_cast(nullptr_t, 0ul);
+
+ struct indet_mem {
+ unsigned char data[sizeof(void *)];
+ };
+ indet_mem im = __builtin_bit_cast(indet_mem, nullptr);
+ nullptr_t npt2 = __builtin_bit_cast(nullptr_t, im);
+
+ return 0;
+}
+
+constexpr int ttn = test_to_nullptr();
+
+namespace IndeterminateToPrimitive {
+ struct S {
+ bool a;
+ // One byte of padding.
+ short b;
+ };
+ constexpr S s{true, 12};
+
+ static_assert(sizeof(S) == sizeof(int), "");
+ constexpr int A = __builtin_bit_cast(int, s); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{indeterminate value}} \
+ // expected-error {{must be initialized by a constant expression}} \
+ // expected-note {{indeterminate value}}
+}
+
+namespace test_partially_initialized {
+ struct pad {
+ signed char x;
+ int y;
+ };
+
+ struct no_pad {
+ signed char x;
+ signed char p1, p2, p3;
+ int y;
+ };
+
+ static_assert(sizeof(pad) == sizeof(no_pad));
+
+ constexpr pad pir{4, 4};
+ // expected-error at +2 {{constexpr variable 'piw' must be initialized by a constant expression}}
+ // expected-note at +1 {{in call to 'bit_cast(pir)'}}
+ constexpr int piw = bit_cast<no_pad>(pir).x;
+ // ref-error at -1 {{constexpr variable 'piw' must be initialized by a constant expression}}
+ // ref-note at -2 {{in call to}}
+
+ // expected-error at +2 {{constexpr variable 'bad' must be initialized by a constant expression}}
+ // expected-note at +1 {{in call to 'bit_cast(pir)'}}
+ constexpr no_pad bad = bit_cast<no_pad>(pir);
+ // ref-error at -1 {{constexpr variable 'bad' must be initialized by a constant expression}}
+ // ref-note at -2 {{in call to}}
+
+ constexpr pad fine = bit_cast<pad>(no_pad{1, 2, 3, 4, 5});
+ static_assert(fine.x == 1 && fine.y == 5);
+}
+
+namespace bad_types {
+ union X {
+ int x;
+ };
+
+ struct G {
+ int g;
+ };
+ // ref-error at +2 {{constexpr variable 'g' must be initialized by a constant expression}}
+ // ref-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 -1 {{constexpr variable 'g' must be initialized by a constant expression}}
+ // expected-note at -2 {{bit_cast from a union type is not allowed in a constant expression}}
+
+ // ref-error at +2 {{constexpr variable 'x' must be initialized by a constant expression}}
+ // ref-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});
+ // expected-error at -1 {{constexpr variable 'x' must be initialized by a constant expression}}
+ // expected-note at -2 {{bit_cast to a union type is not allowed in a constant expression}}
+
+ struct has_pointer {
+ int *ptr; // ref-note 2{{invalid type 'int *' is a member of}} \
+ // expected-note 2{{invalid type 'int *' is a member of}}
+ };
+
+ // ref-error at +2 {{constexpr variable 'ptr' must be initialized by a constant expression}}
+ // ref-note at +1 {{bit_cast from a pointer type is not allowed in a constant expression}}
+ constexpr unsigned long ptr = __builtin_bit_cast(unsigned long, has_pointer{0});
+ // expected-error at -1 {{constexpr variable 'ptr' must be initialized by a constant expression}}
+ // expected-note at -2 {{bit_cast from a pointer type is not allowed in a constant expression}}
+
+
+ // ref-error at +2 {{constexpr variable 'hptr' must be initialized by a constant expression}}
+ // ref-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);
+ // expected-error at -1 {{constexpr variable 'hptr' must be initialized by a constant expression}}
+ // expected-note at -2 {{bit_cast to a pointer type is not allowed in a constant expression}}
+}
+
+namespace backtrace {
+ struct A {
+ int *ptr; // expected-note {{invalid type 'int *' is a member of 'backtrace::A'}} \
+ // ref-note {{invalid type 'int *' is a member of 'backtrace::A'}}
+ };
+
+ struct B {
+ A as[10]; // expected-note {{invalid type 'A[10]' is a member of 'backtrace::B'}} \
+ // ref-note {{invalid type 'A[10]' is a member of 'backtrace::B'}}
+ };
+
+ struct C : B { // expected-note {{invalid type 'B' is a base of 'backtrace::C'}} \
+ // ref-note {{invalid type 'B' is a base of 'backtrace::C'}}
+ };
+
+ struct E {
+ unsigned long ar[10];
+ };
+
+ constexpr E e = __builtin_bit_cast(E, C{}); // expected-error {{must be initialized by a constant expression}} \
+ // expected-note {{bit_cast from a pointer type is not allowed}} \
+ // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{bit_cast from a pointer type is not allowed}}
+}
+
+namespace ReferenceMember {
+ struct ref_mem {
+ const int &rm;
+ };
+
+ typedef unsigned long ulong;
+ constexpr int global_int = 0;
+
+ constexpr ulong run_ref_mem = __builtin_bit_cast(ulong, ref_mem{global_int}); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{bit_cast from a type with a reference member}} \
+ // expected-error {{must be initialized by a constant expression}} \
+ // expected-note {{bit_cast from a type with a reference member}}
+}
+
+namespace FromUnion {
+ union u {
+ int im;
+ };
+
+ constexpr int run_u = __builtin_bit_cast(int, u{32}); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{bit_cast from a union type}} \
+ // expected-error {{must be initialized by a constant expression}} \
+ // expected-note {{bit_cast from a union type}}
+}
+
+
+struct vol_mem {
+ volatile int x; // expected-note {{invalid type 'volatile int' is a member of 'vol_mem'}}
+};
+
+namespace VolatileMember {
+ constexpr int run_vol_mem = __builtin_bit_cast(int, vol_mem{43}); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{non-literal type 'vol_mem'}} \
+ // expected-error {{must be initialized by a constant expression}} \
+ // expected-note {{bit_cast from a volatile type}}
+}
+
+namespace MemberPointer {
+ /// FIXME: The diagnostic for bitcasts is properly implemented, but we lack support for member pointers.
+#if 0
+ struct mem_ptr {
+ int vol_mem::*x; // expected-note{{invalid type 'int vol_mem::*' is a member of 'mem_ptr'}}
+ };
+ constexpr int run_mem_ptr = __builtin_bit_cast(unsigned long, mem_ptr{nullptr}); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{bit_cast from a member pointer type}} \
+ // expected-error {{must be initialized by a constant expression}} \
+ // expected-note {{bit_cast from a member pointer type}}
+#endif
+}
+
+ struct A { char c; /* char padding : 8; */ short s; };
+ struct B { unsigned char x[4]; };
+
+ constexpr B one() {
+ A a = {1, 2};
+ return bit_cast<B>(a);
+ }
+ constexpr char good_one = one().x[0] + one().x[2] + one().x[3];
+ // ref-error at +2 {{constexpr variable 'bad_one' must be initialized by a constant expression}}
+ // ref-note at +1 {{read of uninitialized object is not allowed in a constant expression}}
+ constexpr char bad_one = one().x[1];
+ // expected-error at -1 {{constexpr variable 'bad_one' must be initialized by a constant expression}}
+ // expected-note at -2 {{read of uninitialized object is not allowed in a constant expression}}
+
+
+ constexpr A two() {
+ B b = one(); // b.x[1] is indeterminate.
+ b.x[0] = 'a';
+ b.x[2] = 1;
+ b.x[3] = 2;
+ return bit_cast<A>(b);
+ }
+ constexpr short good_two = two().c + two().s;
+
+
+ namespace std {
+ enum byte : unsigned char {};
+ }
+
+ enum my_byte : unsigned char {};
+
+ struct pad {
+ char a;
+ int b;
+ };
+
+ constexpr int ok_byte = (__builtin_bit_cast(std::byte[8], pad{1, 2}), 0);
+ constexpr int ok_uchar = (__builtin_bit_cast(unsigned char[8], pad{1, 2}), 0);
+
+#ifdef __CHAR_UNSIGNED__
+ // ref-note at +5 {{indeterminate value can only initialize an object of type 'unsigned char', 'char', or 'std::byte'; 'my_byte' is invalid}}}}
+#else
+ // ref-note at +3 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'my_byte' is invalid}}
+#endif
+ // ref-error at +1 {{must be initialized by a constant expression}}
+ constexpr int bad_my_byte = (__builtin_bit_cast(my_byte[8], pad{1, 2}), 0); // {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'my_byte' is invalid}}
+
+#ifndef __CHAR_UNSIGNED__
+ // ref-error at +3 {{must be initialized by a constant expression}}
+ // ref-note at +2 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'char' is invalid}}
+#endif
+ constexpr int bad_char = (__builtin_bit_cast(char[8], pad{1, 2}), 0);
+
+ struct pad_buffer { unsigned char data[sizeof(pad)]; };
+ constexpr bool test_pad_buffer() {
+ pad x = {1, 2};
+ pad_buffer y = __builtin_bit_cast(pad_buffer, x);
+ pad z = __builtin_bit_cast(pad, y);
+ return x.a == z.a && x.b == z.b;
+ }
+ static_assert(test_pad_buffer());
+
+namespace ValueRepr {
+ /// FIXME: This is broken.
+ constexpr bool b = __builtin_bit_cast(bool, (char)123); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{value 123 cannot be represented in type 'bool'}}
+}
+
+namespace FloatMember {
+ struct A {
+ float a;
+ };
+ struct B {
+ unsigned char a[4];
+ };
+
+ constexpr B b = __builtin_bit_cast(B, A{1.0});
+
+#if LITTLE_END
+ static_assert(b.a[0] == 0, "");
+ static_assert(b.a[1] == 0, "");
+ static_assert(b.a[2] == 128, "");
+ static_assert(b.a[3] == 63, "");
+#else
+ static_assert(b.a[0] == 63, "");
+ static_assert(b.a[1] == 128, "");
+ static_assert(b.a[2] == 0, "");
+ static_assert(b.a[3] == 0, "");
+#endif
+
+ constexpr A a = __builtin_bit_cast(A, B{{1 << 6, 0, 0, 1 << 6}});
+ static_assert(static_cast<int>(a.a) == 2, "");
+}
+
+namespace Misc {
+ struct A {
+ decltype(nullptr) a;
+ };
+ /// Not sure why this doesn't work in the current interpreter; GCC accepts it.
+ constexpr unsigned long long L = __builtin_bit_cast(unsigned long long, A{nullptr}); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{indeterminate value}}
+
+ /// Bitcast into a nullptr_t field.
+ struct B {
+ unsigned char a[8];
+ };
+
+ constexpr A a = __builtin_bit_cast(A, B{{0, 0, 0, 0, 0, 0, 0, 0}});
+ static_assert(a.a == nullptr, "");
+
+ /// Uninitialized local variable bitcast'ed to a uchar.
+ constexpr int primUChar() {
+ signed char A;
+ unsigned char B = __builtin_bit_cast(unsigned char, A);
+ /// FIXME: The new interpreter doesn't print the proper diagnostic here; The read from B
+ /// should be uninitialized, since the bitcast returns indeterminate bits. However,
+ /// the code uses primitive values and those are always initialized.
+ return B; // ref-note {{read of uninitialized object}}
+ }
+ static_assert(primUChar() == 0, ""); // ref-error {{not an integral constant expression}} \
+ // ref-note {{in call to}} \
+ // expected-error {{not an integral constant expression}}
+
+ constexpr int primUChar2() {
+ signed char A;
+ unsigned char B = __builtin_bit_cast(unsigned char, A) + 1;
+ /// Same problem as above, but the diagnostic of the current interpreter is just as bad as ours.
+ return B;
+ }
+ static_assert(primUChar2() == 0, ""); // ref-error {{not an integral constant expression}} \
+ // expected-error {{not an integral constant expression}}
+
+ /// This time, the uchar is in a struct.
+ constexpr int primUChar3() {
+ struct B {
+ unsigned char b;
+ };
+ signed char A;
+ B b = __builtin_bit_cast(B, A);
+ return b.b; // ref-note {{read of uninitialized object}} \
+ // expected-note {{read of uninitialized object}}
+ }
+ static_assert(primUChar3() == 0, ""); // ref-error {{not an integral constant expression}} \
+ // ref-note {{in call to}} \
+ // expected-error {{not an integral constant expression}} \
+ // expected-note {{in call to}}
+
+ /// Fully initialized local variable that should end up being un-initalized again because the
+ /// bit cast returns bits of indeterminate value.
+ constexpr int primUChar4() {
+ unsigned char c = 'a';
+ signed char cu;
+
+ c = __builtin_bit_cast(unsigned char, cu);
+
+ return c; // ref-note {{read of uninitialized object}}
+ }
+ static_assert(primUChar4() == 0, ""); // ref-error {{not an integral constant expression}} \
+ // ref-note {{in call to}} \
+ // expected-error {{not an integral constant expression}}
+
+
+ /// Casting an uninitialized struct.
+ constexpr int primUChar5(bool DoBC) {
+ struct A {
+ unsigned char a; // ref-note {{subobject declared here}}
+ };
+ struct B {
+ signed char b;
+ };
+
+ A a = {12};
+
+ if (DoBC) {
+ B b;
+ a = __builtin_bit_cast(A, b); // expected-note {{in call to}} \
+ // expected-note {{read of uninitialized object}} \
+ // ref-note {{subobject 'a' is not initialized}} \
+ // ref-note {{in call to}}
+ }
+
+ return a.a;
+ }
+ static_assert(primUChar5(false) == 12, "");
+ static_assert(primUChar5(true) == 12, ""); // expected-error {{not an integral constant expression}} \
+ // expected-note {{in call to 'primUChar5(true)'}} \
+ // ref-error {{not an integral constant expression}} \
+ // ref-note {{in call to 'primUChar5(true)'}}
+}
+
+
+constexpr unsigned char identity1a = 42;
+constexpr unsigned char identity1b = __builtin_bit_cast(unsigned char, identity1a);
+static_assert(identity1b == 42);
+
+#ifdef __PPC64__
+namespace LongDouble {
+ struct bytes {
+ unsigned char d[sizeof(long double)];
+ };
+
+ constexpr long double ld = 3.1425926539;
+ constexpr long double ldmax = __LDBL_MAX__;
+ static_assert(round_trip<bytes>(ld), "");
+ static_assert(round_trip<bytes>(ldmax), "");
+
+ constexpr bytes b = __builtin_bit_cast(bytes, ld);
+
+#if LITTLE_END
+static_assert(b.d[0] == 0);
+static_assert(b.d[1] == 0);
+static_assert(b.d[2] == 0);
+static_assert(b.d[3] == 0);
+static_assert(b.d[4] == 0);
+static_assert(b.d[5] == 0);
+
+static_assert(b.d[6] == 0);
+static_assert(b.d[7] == 144);
+static_assert(b.d[8] == 62);
+static_assert(b.d[9] == 147);
+static_assert(b.d[10] == 224);
+static_assert(b.d[11] == 121);
+static_assert(b.d[12] == 64);
+static_assert(b.d[13] == 146);
+static_assert(b.d[14] == 0);
+static_assert(b.d[15] == 64);
+#else
+static_assert(b.d[0] == 64);
+static_assert(b.d[1] == 0);
+static_assert(b.d[2] == 146);
+static_assert(b.d[3] == 64);
+static_assert(b.d[4] == 121);
+static_assert(b.d[5] == 224);
+static_assert(b.d[6] == 147);
+static_assert(b.d[7] == 62);
+static_assert(b.d[8] == 144);
+static_assert(b.d[9] == 0);
+
+static_assert(b.d[10] == 0);
+static_assert(b.d[11] == 0);
+static_assert(b.d[12] == 0);
+static_assert(b.d[13] == 0);
+static_assert(b.d[14] == 0);
+static_assert(b.d[15] == 0);
+#endif
+}
+#endif // __PPC64__
+
+#ifdef __x86_64
+namespace LongDoubleX86 {
+ struct bytes {
+ unsigned char d[16]; // ref-note {{declared here}}
+ };
+
+ constexpr long double ld = 3.1425926539;
+ static_assert(round_trip<bytes>(ld), "");
+
+ /// The current interpreter rejects this (probably because the APFloat only
+ /// uses 10 bytes to represent the value instead of the full 16 that the long double
+ /// takes up). MSVC and GCC accept it though.
+ constexpr bytes b = __builtin_bit_cast(bytes, ld); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{subobject 'd' is not initialized}}
+}
+#endif
+
+namespace StringLiterals {
+ template<int n>
+ struct StrBuff {
+ char data[n];
+ };
+
+ constexpr StrBuff<4> Foo = __builtin_bit_cast(StrBuff<4>, "foo"); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note 4{{declared here}}
+ static_assert(Foo.data[0] == 'f', ""); // ref-error {{not an integral constant expression}} \
+ // ref-note {{initializer of 'Foo' is not a constant expression}}
+ static_assert(Foo.data[1] == 'o', ""); // ref-error {{not an integral constant expression}} \
+ // ref-note {{initializer of 'Foo' is not a constant expression}}
+ static_assert(Foo.data[2] == 'o', ""); // ref-error {{not an integral constant expression}} \
+ // ref-note {{initializer of 'Foo' is not a constant expression}}
+ static_assert(Foo.data[3] == '\0', ""); // ref-error {{not an integral constant expression}} \
+ // ref-note {{initializer of 'Foo' is not a constant expression}}
+};
diff --git a/clang/test/AST/Interp/literals.cpp b/clang/test/AST/Interp/literals.cpp
index 00875bcf44dc8e6..67a980e9b5c58ab 100644
--- a/clang/test/AST/Interp/literals.cpp
+++ b/clang/test/AST/Interp/literals.cpp
@@ -186,6 +186,11 @@ constexpr const int *p = &m;
static_assert(p != nullptr, "");
static_assert(*p == 10, "");
+constexpr const void *cp = (void *)p;
+// FIXME: This should be an error in the new interpreter.
+constexpr const int *pm = (int*)cp; // ref-error {{ must be initialized by a constant expression}} \
+ // ref-note {{cast from 'const void *' is not allowed}}
+
constexpr const int* getIntPointer() {
return &m;
}
>From 41f2e9680fbf78b2b7a6a4ac8c97f742bf57aad3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Thu, 5 Oct 2023 11:45:09 +0200
Subject: [PATCH 2/2] Implement bitfield bit casts to prim types
---
clang/lib/AST/Interp/InterpBitcast.cpp | 40 ++++++++++++++--------
clang/lib/AST/Interp/Record.h | 4 +++
clang/test/AST/Interp/builtin-bit-cast.cpp | 25 ++++++++++++++
3 files changed, 54 insertions(+), 15 deletions(-)
diff --git a/clang/lib/AST/Interp/InterpBitcast.cpp b/clang/lib/AST/Interp/InterpBitcast.cpp
index 91326fc8e788080..5d5b5d549d566f3 100644
--- a/clang/lib/AST/Interp/InterpBitcast.cpp
+++ b/clang/lib/AST/Interp/InterpBitcast.cpp
@@ -136,7 +136,7 @@ struct BitcastBuffer {
constexpr BitcastBuffer(std::byte *Buff, size_t BuffSize, bool BigEndian)
: Buff(Buff), BuffSize(BuffSize), BigEndian(BigEndian) {}
- std::byte *getBytes(size_t ByteOffset, size_t N) {
+ std::byte *getBytes(size_t ByteOffset, size_t NBytes, size_t NBits) {
assert(ByteOffset >= this->ByteOffset && "we don't support stepping back");
// All untouched bits before the requested bit offset
@@ -144,14 +144,15 @@ struct BitcastBuffer {
// because they can't be read into non-uchar/non-std::byte
// values.
IndeterminateBits += (ByteOffset - this->ByteOffset);
+ IndeterminateBits += ((NBytes * 8) - NBits);
size_t OldOffset = this->Offset;
- this->Offset += N;
- this->ByteOffset = ByteOffset + N;
+ this->Offset += NBytes;
+ this->ByteOffset = ByteOffset + NBytes;
if (BigEndian)
- return Buff + BuffSize - OldOffset - N;
+ return Buff + BuffSize - OldOffset - NBytes;
// Little Endian target.
return Buff + OldOffset;
@@ -213,7 +214,8 @@ static bool enumerateData(const Pointer &P, const Context &Ctx, size_t Offset,
// TODO: Virtual bases?
for (unsigned I = 0; I != R->getNumFields(); ++I) {
- Pointer Elem = P.atField(R->getField(I)->Offset);
+ const Record::Field *Fi = R->getField(I);
+ Pointer Elem = P.atField(Fi->Offset);
CharUnits FieldOffset =
Ctx.getASTContext().toCharUnitsFromBits(Layout.getFieldOffset(I));
size_t ByteOffset = Offset + FieldOffset.getQuantity();
@@ -302,6 +304,7 @@ static bool CheckBitcastType(InterpState &S, CodePtr OpPC, QualType T,
/// This is used for bitcasting TO a single primitive value.
bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff,
size_t BuffSize, unsigned &IndeterminateBits) {
+ llvm::errs() << __PRETTY_FUNCTION__ << "\n";
assert(P.isLive());
assert(Buff);
assert(BuffSize > 0);
@@ -313,11 +316,10 @@ bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff,
const Context &Ctx = S.getContext();
const ASTContext &ASTCtx = Ctx.getASTContext();
- uint64_t PointerSize =
- ASTCtx
- .toCharUnitsFromBits(
- ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default))
- .getQuantity();
+ uint64_t PointerSizeBits =
+ ASTCtx.getTargetInfo().getPointerWidth(LangAS::Default);
+ uint64_t PointerSizeBytes =
+ ASTCtx.toCharUnitsFromBits(PointerSizeBits).getQuantity();
bool Success = enumeratePointerFields(
P, S.getContext(),
@@ -326,16 +328,24 @@ bool DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &P, std::byte *Buff,
return false;
if (T == PT_Ptr) {
assert(Ptr.getType()->isNullPtrType());
- std::byte *M = F.getBytes(ByteOffset, PointerSize);
- std::memset(M, 0, PointerSize);
+ std::byte *M =
+ F.getBytes(ByteOffset, PointerSizeBytes, PointerSizeBits);
+ std::memset(M, 0, PointerSizeBytes);
return true;
}
+ CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(Ptr.getType());
+ unsigned ObjectReprBytes =
+ ASTCtx.getTypeSizeInChars(Ptr.getType()).getQuantity();
+ unsigned BitWidth;
+ if (const FieldDecl *FD = Ptr.getField(); FD && FD->isBitField())
+ BitWidth = FD->getBitWidthValue(ASTCtx);
+ else
+ BitWidth = ASTCtx.toBits(ObjectReprChars);
+
BITCAST_TYPE_SWITCH_WITH_FLOAT(T, {
T Val = Ptr.deref<T>();
- unsigned ObjectReprBytes =
- ASTCtx.getTypeSizeInChars(Ptr.getType()).getQuantity();
- std::byte *M = F.getBytes(ByteOffset, ObjectReprBytes);
+ std::byte *M = F.getBytes(ByteOffset, ObjectReprBytes, BitWidth);
Val.bitcastToMemory(M);
});
return true;
diff --git a/clang/lib/AST/Interp/Record.h b/clang/lib/AST/Interp/Record.h
index b81070aa77e8413..236add803df62f2 100644
--- a/clang/lib/AST/Interp/Record.h
+++ b/clang/lib/AST/Interp/Record.h
@@ -29,6 +29,10 @@ class Record final {
const FieldDecl *Decl;
unsigned Offset;
Descriptor *Desc;
+ bool isBitfield() const { return Decl->isBitField(); }
+ unsigned getBitWidth(const ASTContext &C) const {
+ return Decl->getBitWidthValue(C);
+ }
};
/// Describes a base class.
diff --git a/clang/test/AST/Interp/builtin-bit-cast.cpp b/clang/test/AST/Interp/builtin-bit-cast.cpp
index c5b8ca12570a138..b057efd25f6b1ca 100644
--- a/clang/test/AST/Interp/builtin-bit-cast.cpp
+++ b/clang/test/AST/Interp/builtin-bit-cast.cpp
@@ -681,3 +681,28 @@ namespace StringLiterals {
static_assert(Foo.data[3] == '\0', ""); // ref-error {{not an integral constant expression}} \
// ref-note {{initializer of 'Foo' is not a constant expression}}
};
+
+/// The current interpreter does not support bitcasts involving bitfields at all,
+/// so the following is mainly from comparing diagnostic output with GCC.
+namespace bitfields {
+ struct S {
+ char a : 8;
+ };
+
+ constexpr S s{4};
+ constexpr char c = __builtin_bit_cast(char, s); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{bit_cast involving bit-field is not yet supported}} \
+ // ref-note{{declared here}}
+ static_assert(c == 4, ""); // ref-error {{not an integral constant expression}} \
+ // ref-note {{initializer of 'c' is not a constant expression}}
+
+
+ struct S2 {
+ char a : 4;
+ };
+ constexpr S2 s2{4};
+ constexpr char c2 = __builtin_bit_cast(char, s2); // expected-error {{must be initialized by a constant expression}} \
+ // expected-note {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'char' is invalid}} \
+ // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{bit_cast involving bit-field is not yet supported}}
+}
More information about the cfe-commits
mailing list