Path: blob/main/contrib/llvm-project/clang/lib/AST/ByteCode/InterpBuiltinBitCast.cpp
213799 views
//===-------------------- InterpBuiltinBitCast.cpp --------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7#include "InterpBuiltinBitCast.h"8#include "BitcastBuffer.h"9#include "Boolean.h"10#include "Context.h"11#include "Floating.h"12#include "Integral.h"13#include "InterpState.h"14#include "MemberPointer.h"15#include "Pointer.h"16#include "Record.h"17#include "clang/AST/ASTContext.h"18#include "clang/AST/RecordLayout.h"19#include "clang/Basic/TargetInfo.h"2021#include <variant>2223using namespace clang;24using namespace clang::interp;2526/// Implement __builtin_bit_cast and related operations.27/// Since our internal representation for data is more complex than28/// something we can simply memcpy or memcmp, we first bitcast all the data29/// into a buffer, which we then later use to copy the data into the target.3031// TODO:32// - Try to minimize heap allocations.33// - Optimize the common case of only pushing and pulling full34// bytes to/from the buffer.3536/// Used to iterate over pointer fields.37using DataFunc =38llvm::function_ref<bool(const Pointer &P, PrimType Ty, Bits BitOffset,39Bits FullBitWidth, bool PackedBools)>;4041#define BITCAST_TYPE_SWITCH(Expr, B) \42do { \43switch (Expr) { \44TYPE_SWITCH_CASE(PT_Sint8, B) \45TYPE_SWITCH_CASE(PT_Uint8, B) \46TYPE_SWITCH_CASE(PT_Sint16, B) \47TYPE_SWITCH_CASE(PT_Uint16, B) \48TYPE_SWITCH_CASE(PT_Sint32, B) \49TYPE_SWITCH_CASE(PT_Uint32, B) \50TYPE_SWITCH_CASE(PT_Sint64, B) \51TYPE_SWITCH_CASE(PT_Uint64, B) \52TYPE_SWITCH_CASE(PT_IntAP, B) \53TYPE_SWITCH_CASE(PT_IntAPS, B) \54TYPE_SWITCH_CASE(PT_Bool, B) \55default: \56llvm_unreachable("Unhandled bitcast type"); \57} \58} while (0)5960#define BITCAST_TYPE_SWITCH_FIXED_SIZE(Expr, B) \61do { \62switch (Expr) { \63TYPE_SWITCH_CASE(PT_Sint8, B) \64TYPE_SWITCH_CASE(PT_Uint8, B) \65TYPE_SWITCH_CASE(PT_Sint16, B) \66TYPE_SWITCH_CASE(PT_Uint16, B) \67TYPE_SWITCH_CASE(PT_Sint32, B) \68TYPE_SWITCH_CASE(PT_Uint32, B) \69TYPE_SWITCH_CASE(PT_Sint64, B) \70TYPE_SWITCH_CASE(PT_Uint64, B) \71TYPE_SWITCH_CASE(PT_Bool, B) \72default: \73llvm_unreachable("Unhandled bitcast type"); \74} \75} while (0)7677/// We use this to recursively iterate over all fields and elements of a pointer78/// and extract relevant data for a bitcast.79static bool enumerateData(const Pointer &P, const Context &Ctx, Bits Offset,80Bits BitsToRead, DataFunc F) {81const Descriptor *FieldDesc = P.getFieldDesc();82assert(FieldDesc);8384// Primitives.85if (FieldDesc->isPrimitive()) {86Bits FullBitWidth =87Bits(Ctx.getASTContext().getTypeSize(FieldDesc->getType()));88return F(P, FieldDesc->getPrimType(), Offset, FullBitWidth,89/*PackedBools=*/false);90}9192// Primitive arrays.93if (FieldDesc->isPrimitiveArray()) {94QualType ElemType = FieldDesc->getElemQualType();95Bits ElemSize = Bits(Ctx.getASTContext().getTypeSize(ElemType));96PrimType ElemT = *Ctx.classify(ElemType);97// Special case, since the bools here are packed.98bool PackedBools =99FieldDesc->getType()->isPackedVectorBoolType(Ctx.getASTContext());100unsigned NumElems = FieldDesc->getNumElems();101bool Ok = true;102for (unsigned I = P.getIndex(); I != NumElems; ++I) {103Ok = Ok && F(P.atIndex(I), ElemT, Offset, ElemSize, PackedBools);104Offset += PackedBools ? Bits(1) : ElemSize;105if (Offset >= BitsToRead)106break;107}108return Ok;109}110111// Composite arrays.112if (FieldDesc->isCompositeArray()) {113QualType ElemType = FieldDesc->getElemQualType();114Bits ElemSize = Bits(Ctx.getASTContext().getTypeSize(ElemType));115for (unsigned I = P.getIndex(); I != FieldDesc->getNumElems(); ++I) {116enumerateData(P.atIndex(I).narrow(), Ctx, Offset, BitsToRead, F);117Offset += ElemSize;118if (Offset >= BitsToRead)119break;120}121return true;122}123124// Records.125if (FieldDesc->isRecord()) {126const Record *R = FieldDesc->ElemRecord;127const ASTRecordLayout &Layout =128Ctx.getASTContext().getASTRecordLayout(R->getDecl());129bool Ok = true;130131for (const Record::Field &Fi : R->fields()) {132if (Fi.isUnnamedBitField())133continue;134Pointer Elem = P.atField(Fi.Offset);135Bits BitOffset =136Offset + Bits(Layout.getFieldOffset(Fi.Decl->getFieldIndex()));137Ok = Ok && enumerateData(Elem, Ctx, BitOffset, BitsToRead, F);138}139for (const Record::Base &B : R->bases()) {140Pointer Elem = P.atField(B.Offset);141CharUnits ByteOffset =142Layout.getBaseClassOffset(cast<CXXRecordDecl>(B.Decl));143Bits BitOffset = Offset + Bits(Ctx.getASTContext().toBits(ByteOffset));144Ok = Ok && enumerateData(Elem, Ctx, BitOffset, BitsToRead, F);145// FIXME: We should only (need to) do this when bitcasting OUT of the146// buffer, not when copying data into it.147if (Ok)148Elem.initialize();149}150151return Ok;152}153154llvm_unreachable("Unhandled data type");155}156157static bool enumeratePointerFields(const Pointer &P, const Context &Ctx,158Bits BitsToRead, DataFunc F) {159return enumerateData(P, Ctx, Bits::zero(), BitsToRead, F);160}161162// This function is constexpr if and only if To, From, and the types of163// all subobjects of To and From are types T such that...164// (3.1) - is_union_v<T> is false;165// (3.2) - is_pointer_v<T> is false;166// (3.3) - is_member_pointer_v<T> is false;167// (3.4) - is_volatile_v<T> is false; and168// (3.5) - T has no non-static data members of reference type169//170// NOTE: This is a version of checkBitCastConstexprEligibilityType() in171// ExprConstant.cpp.172static bool CheckBitcastType(InterpState &S, CodePtr OpPC, QualType T,173bool IsToType) {174enum {175E_Union = 0,176E_Pointer,177E_MemberPointer,178E_Volatile,179E_Reference,180};181enum { C_Member, C_Base };182183auto diag = [&](int Reason) -> bool {184const Expr *E = S.Current->getExpr(OpPC);185S.FFDiag(E, diag::note_constexpr_bit_cast_invalid_type)186<< static_cast<int>(IsToType) << (Reason == E_Reference) << Reason187<< E->getSourceRange();188return false;189};190auto note = [&](int Construct, QualType NoteType, SourceRange NoteRange) {191S.Note(NoteRange.getBegin(), diag::note_constexpr_bit_cast_invalid_subtype)192<< NoteType << Construct << T.getUnqualifiedType() << NoteRange;193return false;194};195196T = T.getCanonicalType();197198if (T->isUnionType())199return diag(E_Union);200if (T->isPointerType())201return diag(E_Pointer);202if (T->isMemberPointerType())203return diag(E_MemberPointer);204if (T.isVolatileQualified())205return diag(E_Volatile);206207if (const RecordDecl *RD = T->getAsRecordDecl()) {208if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {209for (const CXXBaseSpecifier &BS : CXXRD->bases()) {210if (!CheckBitcastType(S, OpPC, BS.getType(), IsToType))211return note(C_Base, BS.getType(), BS.getBeginLoc());212}213}214for (const FieldDecl *FD : RD->fields()) {215if (FD->getType()->isReferenceType())216return diag(E_Reference);217if (!CheckBitcastType(S, OpPC, FD->getType(), IsToType))218return note(C_Member, FD->getType(), FD->getSourceRange());219}220}221222if (T->isArrayType() &&223!CheckBitcastType(S, OpPC, S.getASTContext().getBaseElementType(T),224IsToType))225return false;226227if (const auto *VT = T->getAs<VectorType>()) {228const ASTContext &ASTCtx = S.getASTContext();229QualType EltTy = VT->getElementType();230unsigned NElts = VT->getNumElements();231unsigned EltSize =232VT->isPackedVectorBoolType(ASTCtx) ? 1 : ASTCtx.getTypeSize(EltTy);233234if ((NElts * EltSize) % ASTCtx.getCharWidth() != 0) {235// The vector's size in bits is not a multiple of the target's byte size,236// so its layout is unspecified. For now, we'll simply treat these cases237// as unsupported (this should only be possible with OpenCL bool vectors238// whose element count isn't a multiple of the byte size).239const Expr *E = S.Current->getExpr(OpPC);240S.FFDiag(E, diag::note_constexpr_bit_cast_invalid_vector)241<< QualType(VT, 0) << EltSize << NElts << ASTCtx.getCharWidth();242return false;243}244245if (EltTy->isRealFloatingType() &&246&ASTCtx.getFloatTypeSemantics(EltTy) == &APFloat::x87DoubleExtended()) {247// The layout for x86_fp80 vectors seems to be handled very inconsistently248// by both clang and LLVM, so for now we won't allow bit_casts involving249// it in a constexpr context.250const Expr *E = S.Current->getExpr(OpPC);251S.FFDiag(E, diag::note_constexpr_bit_cast_unsupported_type) << EltTy;252return false;253}254}255256return true;257}258259bool clang::interp::readPointerToBuffer(const Context &Ctx,260const Pointer &FromPtr,261BitcastBuffer &Buffer,262bool ReturnOnUninit) {263const ASTContext &ASTCtx = Ctx.getASTContext();264Endian TargetEndianness =265ASTCtx.getTargetInfo().isLittleEndian() ? Endian::Little : Endian::Big;266267return enumeratePointerFields(268FromPtr, Ctx, Buffer.size(),269[&](const Pointer &P, PrimType T, Bits BitOffset, Bits FullBitWidth,270bool PackedBools) -> bool {271Bits BitWidth = FullBitWidth;272273if (const FieldDecl *FD = P.getField(); FD && FD->isBitField())274BitWidth = Bits(std::min(FD->getBitWidthValue(),275(unsigned)FullBitWidth.getQuantity()));276else if (T == PT_Bool && PackedBools)277BitWidth = Bits(1);278279if (BitWidth.isZero())280return true;281282// Bits will be left uninitialized and diagnosed when reading.283if (!P.isInitialized())284return true;285286if (T == PT_Ptr) {287assert(P.getType()->isNullPtrType());288// Clang treats nullptr_t has having NO bits in its value289// representation. So, we accept it here and leave its bits290// uninitialized.291return true;292}293294assert(P.isInitialized());295auto Buff = std::make_unique<std::byte[]>(FullBitWidth.roundToBytes());296// Work around floating point types that contain unused padding bytes.297// This is really just `long double` on x86, which is the only298// fundamental type with padding bytes.299if (T == PT_Float) {300const Floating &F = P.deref<Floating>();301Bits NumBits = Bits(302llvm::APFloatBase::getSizeInBits(F.getAPFloat().getSemantics()));303assert(NumBits.isFullByte());304assert(NumBits.getQuantity() <= FullBitWidth.getQuantity());305F.bitcastToMemory(Buff.get());306// Now, only (maybe) swap the actual size of the float, excluding307// the padding bits.308if (llvm::sys::IsBigEndianHost)309swapBytes(Buff.get(), NumBits.roundToBytes());310311Buffer.markInitialized(BitOffset, NumBits);312} else {313BITCAST_TYPE_SWITCH(T, { P.deref<T>().bitcastToMemory(Buff.get()); });314315if (llvm::sys::IsBigEndianHost)316swapBytes(Buff.get(), FullBitWidth.roundToBytes());317Buffer.markInitialized(BitOffset, BitWidth);318}319320Buffer.pushData(Buff.get(), BitOffset, BitWidth, TargetEndianness);321return true;322});323}324325bool clang::interp::DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr,326std::byte *Buff, Bits BitWidth, Bits FullBitWidth,327bool &HasIndeterminateBits) {328assert(Ptr.isLive());329assert(Ptr.isBlockPointer());330assert(Buff);331assert(BitWidth <= FullBitWidth);332assert(FullBitWidth.isFullByte());333assert(BitWidth.isFullByte());334335BitcastBuffer Buffer(FullBitWidth);336size_t BuffSize = FullBitWidth.roundToBytes();337QualType DataType = Ptr.getFieldDesc()->getDataType(S.getASTContext());338if (!CheckBitcastType(S, OpPC, DataType, /*IsToType=*/false))339return false;340341bool Success = readPointerToBuffer(S.getContext(), Ptr, Buffer,342/*ReturnOnUninit=*/false);343HasIndeterminateBits = !Buffer.rangeInitialized(Bits::zero(), BitWidth);344345const ASTContext &ASTCtx = S.getASTContext();346Endian TargetEndianness =347ASTCtx.getTargetInfo().isLittleEndian() ? Endian::Little : Endian::Big;348auto B =349Buffer.copyBits(Bits::zero(), BitWidth, FullBitWidth, TargetEndianness);350351std::memcpy(Buff, B.get(), BuffSize);352353if (llvm::sys::IsBigEndianHost)354swapBytes(Buff, BitWidth.roundToBytes());355356return Success;357}358bool clang::interp::DoBitCastPtr(InterpState &S, CodePtr OpPC,359const Pointer &FromPtr, Pointer &ToPtr) {360const ASTContext &ASTCtx = S.getASTContext();361CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(ToPtr.getType());362363return DoBitCastPtr(S, OpPC, FromPtr, ToPtr, ObjectReprChars.getQuantity());364}365366bool clang::interp::DoBitCastPtr(InterpState &S, CodePtr OpPC,367const Pointer &FromPtr, Pointer &ToPtr,368size_t Size) {369assert(FromPtr.isLive());370assert(FromPtr.isBlockPointer());371assert(ToPtr.isBlockPointer());372373QualType FromType = FromPtr.getFieldDesc()->getDataType(S.getASTContext());374QualType ToType = ToPtr.getFieldDesc()->getDataType(S.getASTContext());375376if (!CheckBitcastType(S, OpPC, ToType, /*IsToType=*/true))377return false;378if (!CheckBitcastType(S, OpPC, FromType, /*IsToType=*/false))379return false;380381const ASTContext &ASTCtx = S.getASTContext();382BitcastBuffer Buffer(Bytes(Size).toBits());383readPointerToBuffer(S.getContext(), FromPtr, Buffer,384/*ReturnOnUninit=*/false);385386// Now read the values out of the buffer again and into ToPtr.387Endian TargetEndianness =388ASTCtx.getTargetInfo().isLittleEndian() ? Endian::Little : Endian::Big;389bool Success = enumeratePointerFields(390ToPtr, S.getContext(), Buffer.size(),391[&](const Pointer &P, PrimType T, Bits BitOffset, Bits FullBitWidth,392bool PackedBools) -> bool {393QualType PtrType = P.getType();394if (T == PT_Float) {395const auto &Semantics = ASTCtx.getFloatTypeSemantics(PtrType);396Bits NumBits = Bits(llvm::APFloatBase::getSizeInBits(Semantics));397assert(NumBits.isFullByte());398assert(NumBits.getQuantity() <= FullBitWidth.getQuantity());399auto M = Buffer.copyBits(BitOffset, NumBits, FullBitWidth,400TargetEndianness);401402if (llvm::sys::IsBigEndianHost)403swapBytes(M.get(), NumBits.roundToBytes());404405Floating R = S.allocFloat(Semantics);406Floating::bitcastFromMemory(M.get(), Semantics, &R);407P.deref<Floating>() = R;408P.initialize();409return true;410}411412Bits BitWidth;413if (const FieldDecl *FD = P.getField(); FD && FD->isBitField())414BitWidth = Bits(std::min(FD->getBitWidthValue(),415(unsigned)FullBitWidth.getQuantity()));416else if (T == PT_Bool && PackedBools)417BitWidth = Bits(1);418else419BitWidth = FullBitWidth;420421// If any of the bits are uninitialized, we need to abort unless the422// target type is std::byte or unsigned char.423bool Initialized = Buffer.rangeInitialized(BitOffset, BitWidth);424if (!Initialized) {425if (!PtrType->isStdByteType() &&426!PtrType->isSpecificBuiltinType(BuiltinType::UChar) &&427!PtrType->isSpecificBuiltinType(BuiltinType::Char_U)) {428const Expr *E = S.Current->getExpr(OpPC);429S.FFDiag(E, diag::note_constexpr_bit_cast_indet_dest)430<< PtrType << S.getLangOpts().CharIsSigned431<< E->getSourceRange();432433return false;434}435return true;436}437438auto Memory = Buffer.copyBits(BitOffset, BitWidth, FullBitWidth,439TargetEndianness);440if (llvm::sys::IsBigEndianHost)441swapBytes(Memory.get(), FullBitWidth.roundToBytes());442443BITCAST_TYPE_SWITCH_FIXED_SIZE(T, {444if (BitWidth.nonZero())445P.deref<T>() = T::bitcastFromMemory(Memory.get(), T::bitWidth())446.truncate(BitWidth.getQuantity());447else448P.deref<T>() = T::zero();449});450P.initialize();451return true;452});453454return Success;455}456457using PrimTypeVariant =458std::variant<Pointer, FunctionPointer, MemberPointer, FixedPoint,459Integral<8, false>, Integral<8, true>, Integral<16, false>,460Integral<16, true>, Integral<32, false>, Integral<32, true>,461Integral<64, false>, Integral<64, true>, IntegralAP<true>,462IntegralAP<false>, Boolean, Floating>;463464// NB: This implementation isn't exactly ideal, but:465// 1) We can't just do a bitcast here since we need to be able to466// copy pointers.467// 2) This also needs to handle overlapping regions.468// 3) We currently have no way of iterating over the fields of a pointer469// backwards.470bool clang::interp::DoMemcpy(InterpState &S, CodePtr OpPC,471const Pointer &SrcPtr, const Pointer &DestPtr,472Bits Size) {473assert(SrcPtr.isBlockPointer());474assert(DestPtr.isBlockPointer());475476llvm::SmallVector<PrimTypeVariant> Values;477enumeratePointerFields(SrcPtr, S.getContext(), Size,478[&](const Pointer &P, PrimType T, Bits BitOffset,479Bits FullBitWidth, bool PackedBools) -> bool {480TYPE_SWITCH(T, { Values.push_back(P.deref<T>()); });481return true;482});483484unsigned ValueIndex = 0;485enumeratePointerFields(DestPtr, S.getContext(), Size,486[&](const Pointer &P, PrimType T, Bits BitOffset,487Bits FullBitWidth, bool PackedBools) -> bool {488TYPE_SWITCH(T, {489P.deref<T>() = std::get<T>(Values[ValueIndex]);490P.initialize();491});492493++ValueIndex;494return true;495});496497// We should've read all the values into DestPtr.498assert(ValueIndex == Values.size());499500return true;501}502503504