[clang][bytecode] Start implementing __builtin_bit_cast (#112126)
This is a subset of #68288, with hopefully narrower scope. It does not support bitcasting to non-integral types yet. Bitfields are supported, but only if they result in a full byte-sized final buffer. It does not support casting from null-pointers yet or casting from indeterminate bits. The tests are from #68288 and partially from #74775. The `BitcastBuffer` struct is currently always working in single bits, but I plan to (try to) optimize this for the common full-byte case.
This commit is contained in:
@@ -81,6 +81,16 @@ public:
|
||||
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -374,6 +374,7 @@ private:
|
||||
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);
|
||||
|
||||
|
||||
@@ -135,6 +135,11 @@ public:
|
||||
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 *) +
|
||||
|
||||
@@ -70,6 +70,7 @@ private:
|
||||
// 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();
|
||||
@@ -154,6 +155,18 @@ public:
|
||||
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);
|
||||
|
||||
@@ -171,6 +171,12 @@ public:
|
||||
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());
|
||||
|
||||
@@ -1574,6 +1574,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)
|
||||
|
||||
@@ -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) {
|
||||
@@ -3039,6 +3042,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
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@@ -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"
|
||||
|
||||
367
clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
Normal file
367
clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
Normal file
@@ -0,0 +1,367 @@
|
||||
//===-------------------- 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
26
clang/lib/AST/ByteCode/InterpBuiltinBitCast.h
Normal file
26
clang/lib/AST/ByteCode/InterpBuiltinBitCast.h
Normal file
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
451
clang/test/AST/ByteCode/builtin-bit-cast.cpp
Normal file
451
clang/test/AST/ByteCode/builtin-bit-cast.cpp
Normal file
@@ -0,0 +1,451 @@
|
||||
// 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 NullPtr {
|
||||
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);
|
||||
}
|
||||
|
||||
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@+1 {{subobject declared here}}
|
||||
unsigned char mem[sizeof(BF)];
|
||||
};
|
||||
// expected-error@+2 {{initialized by a constant expression}}
|
||||
// expected-note@+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@+2 {{initialized by a constant expression}}
|
||||
// expected-note@+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@+2 {{initialized by a constant expression}}
|
||||
// expected-note@+1 {{read of uninitialized object is not allowed in a constant expression}}
|
||||
constexpr auto _bad = g().b2;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
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}}
|
||||
|
||||
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 {
|
||||
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() {
|
||||
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;
|
||||
};
|
||||
// expected-error@+2 {{constexpr variable 'g' must be initialized by a constant expression}}
|
||||
// expected-note@+1 {{bit_cast from a union type is not allowed in a constant expression}}
|
||||
constexpr G g = __builtin_bit_cast(G, X{0});
|
||||
// expected-error@+2 {{constexpr variable 'x' must be initialized by a constant expression}}
|
||||
// expected-note@+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@+2 {{constexpr variable 'hptr' must be initialized by a constant expression}}
|
||||
// expected-note@+1 {{bit_cast to a pointer type is not allowed in a constant expression}}
|
||||
constexpr has_pointer hptr = __builtin_bit_cast(has_pointer, 0ul);
|
||||
#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@+2 {{constexpr variable 'run_vol_mem' must be initialized by a constant expression}}
|
||||
// both-note@+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@+2 {{constexpr variable 'run_mem_ptr' must be initialized by a constant expression}}
|
||||
// both-note@+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@+2 {{constexpr variable 'run_ref_mem' must be initialized by a constant expression}}
|
||||
// both-note@+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});
|
||||
Reference in New Issue
Block a user