Index: clang/lib/AST/Interp/Boolean.h =================================================================== --- clang/lib/AST/Interp/Boolean.h +++ clang/lib/AST/Interp/Boolean.h @@ -105,6 +105,13 @@ return Boolean(!Value.isZero()); } + static Boolean bitcastFromMemory(const std::byte *Buff) { + bool Val = static_cast(*Buff); + return Boolean(Val); + } + + void bitcastToMemory(std::byte *Buff) { std::memcpy(Buff, &V, sizeof(V)); } + static Boolean zero() { return from(false); } template Index: clang/lib/AST/Interp/ByteCodeExprGen.h =================================================================== --- clang/lib/AST/Interp/ByteCodeExprGen.h +++ clang/lib/AST/Interp/ByteCodeExprGen.h @@ -267,6 +267,7 @@ bool emitRecordDestruction(const Descriptor *Desc); bool emitDerivedToBaseCasts(const RecordType *DerivedType, const RecordType *BaseType, const Expr *E); + bool emitBuiltinBitCast(const CastExpr *E); protected: /// Variable to storage mapping. Index: clang/lib/AST/Interp/ByteCodeExprGen.cpp =================================================================== --- clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -63,6 +63,53 @@ } // namespace interp } // namespace clang +template +bool ByteCodeExprGen::emitBuiltinBitCast(const CastExpr *E) { + const Expr *SubExpr = E->getSubExpr(); + QualType FromType = SubExpr->getType(); + QualType ToType = E->getType(); + + // A few invariants. These are invalid for bitcasts. + // FIXME: Diagnostics. + if (ToType->isArrayType() || ToType->isPointerType()) + return false; + + assert(!ToType->isReferenceType()); + + // Get a pointer to the value-to-cast on the stack. + if (!this->visit(SubExpr)) + return false; + + std::optional FromT = classify(FromType); + std::optional ToT = classify(ToType); + + if (!ToT) { + // Conversion to an array or record type. + return this->emitBitCastPtr(E); + } + + // FIXME: Diagnostics. + if (*ToT == PT_Ptr) + return false; + + assert(ToT); + // Source and dest type are the same, just emit a load. + if (FromT && *FromT == ToT) + return this->emitLoad(*ToT, E); + + // 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); + return this->emitBitCastFP(TargetSemantics, E); + } + + return this->emitBitCast(*ToT, E); +} + template bool ByteCodeExprGen::VisitCastExpr(const CastExpr *CE) { auto *SubExpr = CE->getSubExpr(); @@ -83,6 +130,9 @@ }); } + case CK_LValueToRValueBitCast: + return this->emitBuiltinBitCast(CE); + case CK_UncheckedDerivedToBase: case CK_DerivedToBase: { if (!this->visit(SubExpr)) @@ -137,6 +187,7 @@ case CK_NonAtomicToAtomic: case CK_NoOp: case CK_UserDefinedConversion: + case CK_BitCast: return this->visit(SubExpr); case CK_IntegralToBoolean: @@ -1471,6 +1522,8 @@ return this->visit(CE); } else if (const auto *DIE = dyn_cast(Initializer)) { return this->visitInitializer(DIE->getExpr()); + } else if (const auto *BBCE = dyn_cast(Initializer)) { + return this->visit(BBCE); } else if (const auto *CE = dyn_cast(Initializer)) { return this->visitInitializer(CE->getSubExpr()); } else if (const auto *CE = dyn_cast(Initializer)) { Index: clang/lib/AST/Interp/Descriptor.h =================================================================== --- clang/lib/AST/Interp/Descriptor.h +++ clang/lib/AST/Interp/Descriptor.h @@ -174,6 +174,8 @@ /// Checks if the descriptor is of an array of primitives. bool isPrimitiveArray() const { return IsArray && !ElemDesc; } + /// Checks if the descriptor is of an array of composites. + bool isCompositeArray() const { return IsArray && ElemDesc; } /// Checks if the descriptor is of an array of zero size. bool isZeroSizeArray() const { return Size == 0; } /// Checks if the descriptor is of an array of unknown size. Index: clang/lib/AST/Interp/Floating.h =================================================================== --- clang/lib/AST/Interp/Floating.h +++ clang/lib/AST/Interp/Floating.h @@ -103,6 +103,20 @@ return Status; } + static Floating bitcastFromMemory(const std::byte *Buff, + const llvm::fltSemantics &Sem) { + size_t Size = APFloat::semanticsSizeInBits(Sem); + llvm::APInt API(Size, true); + llvm::LoadIntFromMemory(API, (const uint8_t *)Buff, Size / 8); + + return Floating(APFloat(Sem, API)); + } + + void bitcastToMemory(std::byte *Buff) { + llvm::APInt API = F.bitcastToAPInt(); + llvm::StoreIntToMemory(API, (uint8_t *)Buff, bitWidth() / 8); + } + // ------- static APFloat::opStatus add(const Floating &A, const Floating &B, Index: clang/lib/AST/Interp/Integral.h =================================================================== --- clang/lib/AST/Interp/Integral.h +++ clang/lib/AST/Interp/Integral.h @@ -173,6 +173,17 @@ 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 { + std::memcpy(Buff, &V, sizeof(ReprT)); + } + static bool inRange(int64_t Value, unsigned NumBits) { return CheckRange(Value); } Index: clang/lib/AST/Interp/Interp.h =================================================================== --- clang/lib/AST/Interp/Interp.h +++ clang/lib/AST/Interp/Interp.h @@ -149,6 +149,17 @@ /// Interpret a builtin function. bool InterpretBuiltin(InterpState &S, CodePtr &PC, unsigned BuiltinID); +/// 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, const Pointer &P, std::byte *Buff, + size_t BuffSize); + +/// 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); + enum class ArithOp { Add, Sub }; //===----------------------------------------------------------------------===// @@ -1407,6 +1418,49 @@ return true; } +template ::T> +bool BitCast(InterpState &S, CodePtr OpPC) { + const Pointer &FromPtr = S.Stk.pop(); + + constexpr size_t BuffSize = primSize(); + std::byte Buff[BuffSize]; + + if (!DoBitCast(S, FromPtr, Buff, BuffSize)) + return false; + + S.Stk.push(ToT::bitcastFromMemory(Buff)); + 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 &ToPtr = S.Stk.peek(); + + if (!DoBitCastToPtr(S, FromPtr, ToPtr)) + return false; + + return true; +} + +/// Bitcast TO a float. +inline bool BitCastFP(InterpState &S, CodePtr OpPC, + const llvm::fltSemantics *Sem) { + const Pointer &FromPtr = S.Stk.pop(); + + size_t BuffSize = APFloat::semanticsSizeInBits(*Sem) / 8; + std::vector Buff(BuffSize); + + if (!DoBitCast(S, FromPtr, Buff.data(), BuffSize)) + return false; + + S.Stk.push(Floating::bitcastFromMemory(Buff.data(), *Sem)); + return true; +} + /// 1) Pops a Floating from the stack. /// 2) Pushes a new floating on the stack that uses the given semantics. /// Not templated, so implemented in Interp.cpp. Index: clang/lib/AST/Interp/InterpBuiltin.cpp =================================================================== --- clang/lib/AST/Interp/InterpBuiltin.cpp +++ clang/lib/AST/Interp/InterpBuiltin.cpp @@ -9,6 +9,7 @@ #include "Interp.h" #include "PrimType.h" #include "clang/Basic/Builtins.h" +#include "clang/Basic/TargetInfo.h" namespace clang { namespace interp { @@ -29,5 +30,108 @@ return false; } +struct Foo { + std::byte *Buff; + size_t Offset; + size_t BuffSize; + bool BigEndian; + + constexpr Foo(std::byte *Buff, size_t BuffSize, bool BigEndian) + : Buff(Buff), Offset(0), BuffSize(BuffSize), BigEndian(BigEndian) {} + + std::byte *getBytesAndAdvance(size_t N) { + size_t OldOffset = Offset; + Offset += N; + + assert(OldOffset < BuffSize); + + if (BigEndian) { + return Buff + BuffSize - OldOffset - N; + } + + // Little Endian target. + return Buff + OldOffset; + } +}; + +bool DoBitCast(InterpState &S, const Pointer &P, std::byte *Buff, + size_t BuffSize) { + Foo F(Buff, BuffSize, S.getCtx().getTargetInfo().isBigEndian()); + + P.enumerateFields(S.getContext(), [&](const Pointer &Ptr, PrimType Ty) { + if (Ty == PT_Float) { + Floating &Val = Ptr.deref(); + std::byte *M = F.getBytesAndAdvance(Val.bitWidth() / 8); + Val.bitcastToMemory(M); + return; + } + + BITCAST_TYPE_SWITCH(Ty, { + T Val = Ptr.deref(); + unsigned SizeInBytes = Val.bitWidth() / 8; + std::byte *M = F.getBytesAndAdvance(SizeInBytes); + Val.bitcastToMemory(M); + }); + }); + + return true; +} + +/// 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]); +} + +bool DoBitCastToPtr(InterpState &S, const Pointer &P, Pointer &DestPtr) { + const Context &Ctx = S.getContext(); + bool BigEndian = S.getCtx().getTargetInfo().isBigEndian(); + + std::vector Data; + P.enumerateFields(Ctx, [&](const Pointer &P, PrimType Ty) { + BITCAST_TYPE_SWITCH(Ty, { + T Val = P.deref(); + unsigned SizeInBytes = Val.bitWidth() / 8; + unsigned Offset = Data.size(); + Data.resize(Data.size() + SizeInBytes); + Val.bitcastToMemory(Data.data() + Offset); + + if (BigEndian) + swapBytes(Data.data() + Offset, SizeInBytes); + }); + }); + + unsigned Offset = 0; + DestPtr.enumerateFields(Ctx, [&](const Pointer &P, PrimType Ty) { + if (Ty == PT_Float) { + const auto &Sem = Ctx.getASTContext().getFloatTypeSemantics( + P.getFieldDesc()->getType()); + size_t Size = APFloat::semanticsSizeInBits(Sem) / 8; + + std::byte *M = Data.data() + Offset; + + if (BigEndian) + swapBytes(M, Size); + P.deref() = Floating::bitcastFromMemory(M, Sem); + Offset += Size; + return; + } + + BITCAST_TYPE_SWITCH(Ty, { + T &Val = P.deref(); + + size_t Size = Val.bitWidth() / 8; + std::byte *M = Data.data() + Offset; + if (BigEndian) + swapBytes(M, Size); + + Val = T::bitcastFromMemory(M); + P.initialize(); + Offset += Size; + }); + }); + return true; +} + } // namespace interp } // namespace clang Index: clang/lib/AST/Interp/Opcodes.td =================================================================== --- clang/lib/AST/Interp/Opcodes.td +++ clang/lib/AST/Interp/Opcodes.td @@ -551,6 +551,22 @@ 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; +} + +def BitCastPtr : Opcode; + +def BitCastFP : Opcode { + let Types = []; + let Args = [ArgFltSemantics]; +} + def CastFP : Opcode { let Types = []; let Args = [ArgFltSemantics, ArgRoundingMode]; Index: clang/lib/AST/Interp/Pointer.h =================================================================== --- clang/lib/AST/Interp/Pointer.h +++ clang/lib/AST/Interp/Pointer.h @@ -59,6 +59,7 @@ /// │ /// │ /// Base +using DataFunc = llvm::function_ref; class Pointer { private: static constexpr unsigned PastEndMark = ~0u; @@ -337,6 +338,9 @@ /// Deactivates an entire strurcutre. void deactivate() const; + /// Calls F for all fields in this Pointer. + void enumerateFields(const Context &Ctx, DataFunc F) const; + /// Checks if two pointers are comparable. static bool hasSameBase(const Pointer &A, const Pointer &B); /// Checks if two pointers can be subtracted. Index: clang/lib/AST/Interp/Pointer.cpp =================================================================== --- clang/lib/AST/Interp/Pointer.cpp +++ clang/lib/AST/Interp/Pointer.cpp @@ -245,3 +245,54 @@ return Result; } + +static void enumerateData(const Pointer &P, const Context &Ctx, DataFunc F) { + const Descriptor *FieldDesc = P.getFieldDesc(); + assert(FieldDesc); + + // Primitives. + if (FieldDesc->isPrimitive()) { + F(P, *Ctx.classify(FieldDesc->getType())); + return; + } + + // Primitive arrays. + if (FieldDesc->isPrimitiveArray()) { + QualType ElemType = + FieldDesc->getType()->getAsArrayTypeUnsafe()->getElementType(); + PrimType ElemT = *Ctx.classify(ElemType); + for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) { + F(P.atIndex(I), ElemT); + } + return; + } + + // Composite arrays. + if (FieldDesc->isCompositeArray()) { + for (unsigned I = 0; I != FieldDesc->getNumElems(); ++I) + enumerateData(P.atIndex(I).narrow(), Ctx, F); + return; + } + + // Records. + if (FieldDesc->isRecord()) { + const Record *R = FieldDesc->ElemRecord; + for (unsigned I = 0; I != R->getNumBases(); ++I) { + Pointer Elem = P.atField(R->getBase(I)->Offset); + enumerateData(Elem, Ctx, F); + } + // TODO: Virtual bases? + + for (unsigned I = 0; I != R->getNumFields(); ++I) { + Pointer Elem = P.atField(R->getField(I)->Offset); + enumerateData(Elem, Ctx, F); + } + return; + } + + llvm_unreachable("Unhandled data type"); +} + +void Pointer::enumerateFields(const Context &Ctx, DataFunc F) const { + enumerateData(*this, Ctx, F); +} Index: clang/lib/AST/Interp/PrimType.h =================================================================== --- clang/lib/AST/Interp/PrimType.h +++ clang/lib/AST/Interp/PrimType.h @@ -64,6 +64,10 @@ /// Returns the size of a primitive type in bytes. size_t primSize(PrimType Type); +template constexpr size_t primSize() { + return sizeof(typename PrimConv::T); +} + /// Aligns a size to the pointer alignment. constexpr size_t align(size_t Size) { return ((Size + alignof(void *) - 1) / alignof(void *)) * alignof(void *); @@ -100,6 +104,24 @@ TYPE_SWITCH_CASE(PT_FnPtr, B) \ } \ } while (0) + +#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) + #define COMPOSITE_TYPE_SWITCH(Expr, B, D) \ do { \ switch (Expr) { \ Index: clang/test/AST/Interp/builtin-bit-cast.cpp =================================================================== --- /dev/null +++ clang/test/AST/Interp/builtin-bit-cast.cpp @@ -0,0 +1,228 @@ +// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify -std=c++2a -fsyntax-only %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 -std=c++2a -fsyntax-only -triple aarch64_be-linux-gnu -fexperimental-new-constant-interpreter %s + + +/// ref-no-diagnostics + + +/// FIXME: This is a version of clang/test/SemaCXX/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 + +template struct is_same { + static constexpr bool value = false; +}; +template struct is_same { + static constexpr bool value = true; +}; + +static_assert(sizeof(int) == 4); +static_assert(sizeof(long long) == 8); + +template +constexpr To bit_cast(const From &from) { + static_assert(sizeof(To) == sizeof(From)); + return __builtin_bit_cast(To, from); +} + +template +constexpr bool round_trip(const Init &init) { + return bit_cast(bit_cast(init)) == init; +} + +static_assert(round_trip((int)-1)); +static_assert(round_trip((int)0x12345678)); +static_assert(round_trip((int)0x87654321)); +static_assert(round_trip((int)0x0C05FEFE)); +static_assert(round_trip((int)0x0C05FEFE)); + +constexpr unsigned char input[] = {0xCA, 0xFE, 0xBA, 0xBE}; +constexpr unsigned expected = LITTLE_END ? 0xBEBAFECA : 0xCAFEBABE; +static_assert(bit_cast(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(splice) == (LITTLE_END + ? 0xCAFEBABE0C05FEFE + : 0x0C05FEFECAFEBABE)); + +constexpr int_splicer IS = bit_cast(0xCAFEBABE0C05FEFE); +static_assert(bit_cast(0xCAFEBABE0C05FEFE).x == (LITTLE_END + ? 0x0C05FEFE + : 0xCAFEBABE)); + +static_assert(round_trip(splice)); +static_assert(round_trip(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(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(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)); +}; + + +namespace test_array_fill { + constexpr unsigned char a[4] = {1, 2}; + constexpr unsigned int i = bit_cast(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 FloatToDouble { + constexpr float F1[] = {1.0f, 2.0f}; + constexpr double D1 = __builtin_bit_cast(double, F1); + static_assert(D1 > 0); +}; + + +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(s); + static_assert(g.a == 1 && g.b == 2 && g.c == 3); + + static_assert(round_trip(s)); + static_assert(round_trip(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 +} + +typedef decltype(nullptr) nullptr_t; + +/// FIXME: This works in the current interpreter, but it's weird and GCC +/// rejects it as well. +constexpr int test_from_nullptr_pass = (__builtin_bit_cast(unsigned char[8], nullptr), 0); // expected-error {{must be initialized by a constant expression}} + +/// FIXME: The current interpreter allows bitcasting to nullptr_t, but +/// GCC does not. +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(); // expected-error {{must be initialized by a constant expression}} + + + + Index: clang/test/AST/Interp/literals.cpp =================================================================== --- clang/test/AST/Interp/literals.cpp +++ clang/test/AST/Interp/literals.cpp @@ -96,6 +96,11 @@ 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; }