1//===-------------------- InterpBuiltinBitCast.cpp --------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8#include "InterpBuiltinBitCast.h"
9#include "BitcastBuffer.h"
10#include "Boolean.h"
11#include "Context.h"
12#include "Floating.h"
13#include "Integral.h"
14#include "InterpState.h"
15#include "MemberPointer.h"
16#include "Pointer.h"
17#include "Record.h"
18#include "clang/AST/ASTContext.h"
19#include "clang/AST/RecordLayout.h"
20#include "clang/Basic/TargetInfo.h"
21
22#include <variant>
23
24using namespace clang;
25using namespace clang::interp;
26
27/// Implement __builtin_bit_cast and related operations.
28/// Since our internal representation for data is more complex than
29/// something we can simply memcpy or memcmp, we first bitcast all the data
30/// into a buffer, which we then later use to copy the data into the target.
31
32// TODO:
33// - Try to minimize heap allocations.
34// - Optimize the common case of only pushing and pulling full
35// bytes to/from the buffer.
36
37/// Used to iterate over pointer fields.
38using DataFunc =
39 llvm::function_ref<bool(const Pointer &P, PrimType Ty, Bits BitOffset,
40 Bits FullBitWidth, bool PackedBools)>;
41
42#define BITCAST_TYPE_SWITCH(Expr, B) \
43 do { \
44 switch (Expr) { \
45 TYPE_SWITCH_CASE(PT_Sint8, B) \
46 TYPE_SWITCH_CASE(PT_Uint8, B) \
47 TYPE_SWITCH_CASE(PT_Sint16, B) \
48 TYPE_SWITCH_CASE(PT_Uint16, B) \
49 TYPE_SWITCH_CASE(PT_Sint32, B) \
50 TYPE_SWITCH_CASE(PT_Uint32, B) \
51 TYPE_SWITCH_CASE(PT_Sint64, B) \
52 TYPE_SWITCH_CASE(PT_Uint64, B) \
53 TYPE_SWITCH_CASE(PT_IntAP, B) \
54 TYPE_SWITCH_CASE(PT_IntAPS, B) \
55 TYPE_SWITCH_CASE(PT_Bool, B) \
56 default: \
57 llvm_unreachable("Unhandled bitcast type"); \
58 } \
59 } while (0)
60
61#define BITCAST_TYPE_SWITCH_FIXED_SIZE(Expr, B) \
62 do { \
63 switch (Expr) { \
64 TYPE_SWITCH_CASE(PT_Sint8, B) \
65 TYPE_SWITCH_CASE(PT_Uint8, B) \
66 TYPE_SWITCH_CASE(PT_Sint16, B) \
67 TYPE_SWITCH_CASE(PT_Uint16, B) \
68 TYPE_SWITCH_CASE(PT_Sint32, B) \
69 TYPE_SWITCH_CASE(PT_Uint32, B) \
70 TYPE_SWITCH_CASE(PT_Sint64, B) \
71 TYPE_SWITCH_CASE(PT_Uint64, B) \
72 TYPE_SWITCH_CASE(PT_Bool, B) \
73 default: \
74 llvm_unreachable("Unhandled bitcast type"); \
75 } \
76 } while (0)
77
78/// We use this to recursively iterate over all fields and elements of a pointer
79/// and extract relevant data for a bitcast.
80static bool enumerateData(const Pointer &P, const Context &Ctx, Bits Offset,
81 Bits BitsToRead, DataFunc F) {
82 const Descriptor *FieldDesc = P.getFieldDesc();
83 assert(FieldDesc);
84
85 // Primitives.
86 if (FieldDesc->isPrimitive()) {
87 Bits FullBitWidth =
88 Bits(Ctx.getASTContext().getTypeSize(T: FieldDesc->getType()));
89 return F(P, FieldDesc->getPrimType(), Offset, FullBitWidth,
90 /*PackedBools=*/false);
91 }
92
93 // Primitive arrays.
94 if (FieldDesc->isPrimitiveArray()) {
95 QualType ElemType = FieldDesc->getElemQualType();
96 Bits ElemSize = Bits(Ctx.getASTContext().getTypeSize(T: ElemType));
97 PrimType ElemT = *Ctx.classify(T: ElemType);
98 // Special case, since the bools here are packed.
99 bool PackedBools =
100 FieldDesc->getType()->isPackedVectorBoolType(ctx: Ctx.getASTContext());
101 unsigned NumElems = FieldDesc->getNumElems();
102 bool Ok = true;
103 for (unsigned I = P.getIndex(); I != NumElems; ++I) {
104 Ok = Ok && F(P.atIndex(Idx: I), ElemT, Offset, ElemSize, PackedBools);
105 Offset += PackedBools ? Bits(1) : ElemSize;
106 if (Offset >= BitsToRead)
107 break;
108 }
109 return Ok;
110 }
111
112 // Composite arrays.
113 if (FieldDesc->isCompositeArray()) {
114 QualType ElemType = FieldDesc->getElemQualType();
115 Bits ElemSize = Bits(Ctx.getASTContext().getTypeSize(T: ElemType));
116 for (unsigned I = P.getIndex(); I != FieldDesc->getNumElems(); ++I) {
117 enumerateData(P: P.atIndex(Idx: I).narrow(), Ctx, Offset, BitsToRead, F);
118 Offset += ElemSize;
119 if (Offset >= BitsToRead)
120 break;
121 }
122 return true;
123 }
124
125 // Records.
126 if (FieldDesc->isRecord()) {
127 const Record *R = FieldDesc->ElemRecord;
128 const ASTRecordLayout &Layout =
129 Ctx.getASTContext().getASTRecordLayout(D: R->getDecl());
130 bool Ok = true;
131
132 for (const Record::Field &Fi : R->fields()) {
133 if (Fi.isUnnamedBitField())
134 continue;
135 Pointer Elem = P.atField(Off: Fi.Offset);
136 Bits BitOffset =
137 Offset + Bits(Layout.getFieldOffset(FieldNo: Fi.Decl->getFieldIndex()));
138 Ok = Ok && enumerateData(P: Elem, Ctx, Offset: BitOffset, BitsToRead, F);
139 }
140 for (const Record::Base &B : R->bases()) {
141 Pointer Elem = P.atField(Off: B.Offset);
142 CharUnits ByteOffset =
143 Layout.getBaseClassOffset(Base: cast<CXXRecordDecl>(Val: B.Decl));
144 Bits BitOffset = Offset + Bits(Ctx.getASTContext().toBits(CharSize: ByteOffset));
145 Ok = Ok && enumerateData(P: Elem, Ctx, Offset: BitOffset, BitsToRead, F);
146 // FIXME: We should only (need to) do this when bitcasting OUT of the
147 // buffer, not when copying data into it.
148 if (Ok)
149 Elem.initialize();
150 }
151
152 return Ok;
153 }
154
155 llvm_unreachable("Unhandled data type");
156}
157
158static bool enumeratePointerFields(const Pointer &P, const Context &Ctx,
159 Bits BitsToRead, DataFunc F) {
160 return enumerateData(P, Ctx, Offset: Bits::zero(), BitsToRead, F);
161}
162
163// This function is constexpr if and only if To, From, and the types of
164// all subobjects of To and From are types T such that...
165// (3.1) - is_union_v<T> is false;
166// (3.2) - is_pointer_v<T> is false;
167// (3.3) - is_member_pointer_v<T> is false;
168// (3.4) - is_volatile_v<T> is false; and
169// (3.5) - T has no non-static data members of reference type
170//
171// NOTE: This is a version of checkBitCastConstexprEligibilityType() in
172// ExprConstant.cpp.
173static bool CheckBitcastType(InterpState &S, CodePtr OpPC, QualType T,
174 bool IsToType) {
175 enum {
176 E_Union = 0,
177 E_Pointer,
178 E_MemberPointer,
179 E_Volatile,
180 E_Reference,
181 };
182 enum { C_Member, C_Base };
183
184 auto diag = [&](int Reason) -> bool {
185 const Expr *E = S.Current->getExpr(PC: OpPC);
186 S.FFDiag(E, DiagId: diag::note_constexpr_bit_cast_invalid_type)
187 << static_cast<int>(IsToType) << (Reason == E_Reference) << Reason
188 << E->getSourceRange();
189 return false;
190 };
191 auto note = [&](int Construct, QualType NoteType, SourceRange NoteRange) {
192 S.Note(Loc: NoteRange.getBegin(), DiagId: diag::note_constexpr_bit_cast_invalid_subtype)
193 << NoteType << Construct << T.getUnqualifiedType() << NoteRange;
194 return false;
195 };
196
197 T = T.getCanonicalType();
198
199 if (T->isUnionType())
200 return diag(E_Union);
201 if (T->isPointerType())
202 return diag(E_Pointer);
203 if (T->isMemberPointerType())
204 return diag(E_MemberPointer);
205 if (T.isVolatileQualified())
206 return diag(E_Volatile);
207
208 if (const RecordDecl *RD = T->getAsRecordDecl()) {
209 if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(Val: RD)) {
210 for (const CXXBaseSpecifier &BS : CXXRD->bases()) {
211 if (!CheckBitcastType(S, OpPC, T: BS.getType(), IsToType))
212 return note(C_Base, BS.getType(), BS.getBeginLoc());
213 }
214 }
215 for (const FieldDecl *FD : RD->fields()) {
216 if (FD->getType()->isReferenceType())
217 return diag(E_Reference);
218 if (!CheckBitcastType(S, OpPC, T: FD->getType(), IsToType))
219 return note(C_Member, FD->getType(), FD->getSourceRange());
220 }
221 }
222
223 if (T->isArrayType() &&
224 !CheckBitcastType(S, OpPC, T: S.getASTContext().getBaseElementType(QT: T),
225 IsToType))
226 return false;
227
228 if (const auto *VT = T->getAs<VectorType>()) {
229 const ASTContext &ASTCtx = S.getASTContext();
230 QualType EltTy = VT->getElementType();
231 unsigned NElts = VT->getNumElements();
232 unsigned EltSize =
233 VT->isPackedVectorBoolType(ctx: ASTCtx) ? 1 : ASTCtx.getTypeSize(T: EltTy);
234
235 if ((NElts * EltSize) % ASTCtx.getCharWidth() != 0) {
236 // The vector's size in bits is not a multiple of the target's byte size,
237 // so its layout is unspecified. For now, we'll simply treat these cases
238 // as unsupported (this should only be possible with OpenCL bool vectors
239 // whose element count isn't a multiple of the byte size).
240 const Expr *E = S.Current->getExpr(PC: OpPC);
241 S.FFDiag(E, DiagId: diag::note_constexpr_bit_cast_invalid_vector)
242 << QualType(VT, 0) << EltSize << NElts << ASTCtx.getCharWidth();
243 return false;
244 }
245
246 if (EltTy->isRealFloatingType() &&
247 &ASTCtx.getFloatTypeSemantics(T: EltTy) == &APFloat::x87DoubleExtended()) {
248 // The layout for x86_fp80 vectors seems to be handled very inconsistently
249 // by both clang and LLVM, so for now we won't allow bit_casts involving
250 // it in a constexpr context.
251 const Expr *E = S.Current->getExpr(PC: OpPC);
252 S.FFDiag(E, DiagId: diag::note_constexpr_bit_cast_unsupported_type) << EltTy;
253 return false;
254 }
255 }
256
257 return true;
258}
259
260bool clang::interp::readPointerToBuffer(const Context &Ctx,
261 const Pointer &FromPtr,
262 BitcastBuffer &Buffer,
263 bool ReturnOnUninit) {
264 const ASTContext &ASTCtx = Ctx.getASTContext();
265 Endian TargetEndianness =
266 ASTCtx.getTargetInfo().isLittleEndian() ? Endian::Little : Endian::Big;
267
268 return enumeratePointerFields(
269 P: FromPtr, Ctx, BitsToRead: Buffer.size(),
270 F: [&](const Pointer &P, PrimType T, Bits BitOffset, Bits FullBitWidth,
271 bool PackedBools) -> bool {
272 Bits BitWidth = FullBitWidth;
273
274 if (const FieldDecl *FD = P.getField(); FD && FD->isBitField())
275 BitWidth = Bits(std::min(a: FD->getBitWidthValue(),
276 b: (unsigned)FullBitWidth.getQuantity()));
277 else if (T == PT_Bool && PackedBools)
278 BitWidth = Bits(1);
279
280 if (BitWidth.isZero())
281 return true;
282
283 // Bits will be left uninitialized and diagnosed when reading.
284 if (!P.isInitialized())
285 return true;
286
287 if (T == PT_Ptr) {
288 assert(P.getType()->isNullPtrType());
289 // Clang treats nullptr_t has having NO bits in its value
290 // representation. So, we accept it here and leave its bits
291 // uninitialized.
292 return true;
293 }
294
295 assert(P.isInitialized());
296 auto Buff = std::make_unique<std::byte[]>(num: FullBitWidth.roundToBytes());
297 // Work around floating point types that contain unused padding bytes.
298 // This is really just `long double` on x86, which is the only
299 // fundamental type with padding bytes.
300 if (T == PT_Float) {
301 const Floating &F = P.deref<Floating>();
302 Bits NumBits = Bits(
303 llvm::APFloatBase::getSizeInBits(Sem: F.getAPFloat().getSemantics()));
304 assert(NumBits.isFullByte());
305 assert(NumBits.getQuantity() <= FullBitWidth.getQuantity());
306 F.bitcastToMemory(Buff: Buff.get());
307 // Now, only (maybe) swap the actual size of the float, excluding
308 // the padding bits.
309 if (llvm::sys::IsBigEndianHost)
310 swapBytes(M: Buff.get(), N: NumBits.roundToBytes());
311
312 Buffer.markInitialized(Start: BitOffset, Length: NumBits);
313 } else {
314 BITCAST_TYPE_SWITCH(T, { P.deref<T>().bitcastToMemory(Buff.get()); });
315
316 if (llvm::sys::IsBigEndianHost)
317 swapBytes(M: Buff.get(), N: FullBitWidth.roundToBytes());
318 Buffer.markInitialized(Start: BitOffset, Length: BitWidth);
319 }
320
321 Buffer.pushData(In: Buff.get(), BitOffset, BitWidth, TargetEndianness);
322 return true;
323 });
324}
325
326bool clang::interp::DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
327 std::byte *Buff, Bits BitWidth, Bits FullBitWidth,
328 bool &HasIndeterminateBits) {
329 assert(Ptr.isLive());
330 assert(Ptr.isBlockPointer());
331 assert(Buff);
332 assert(BitWidth <= FullBitWidth);
333 assert(FullBitWidth.isFullByte());
334 assert(BitWidth.isFullByte());
335
336 BitcastBuffer Buffer(FullBitWidth);
337 size_t BuffSize = FullBitWidth.roundToBytes();
338 QualType DataType = Ptr.getFieldDesc()->getDataType(Ctx: S.getASTContext());
339 if (!CheckBitcastType(S, OpPC, T: DataType, /*IsToType=*/false))
340 return false;
341
342 bool Success = readPointerToBuffer(Ctx: S.getContext(), FromPtr: Ptr, Buffer,
343 /*ReturnOnUninit=*/false);
344 HasIndeterminateBits = !Buffer.rangeInitialized(Offset: Bits::zero(), Length: BitWidth);
345
346 const ASTContext &ASTCtx = S.getASTContext();
347 Endian TargetEndianness =
348 ASTCtx.getTargetInfo().isLittleEndian() ? Endian::Little : Endian::Big;
349 auto B =
350 Buffer.copyBits(BitOffset: Bits::zero(), BitWidth, FullBitWidth, TargetEndianness);
351
352 std::memcpy(dest: Buff, src: B.get(), n: BuffSize);
353
354 if (llvm::sys::IsBigEndianHost)
355 swapBytes(M: Buff, N: BitWidth.roundToBytes());
356
357 return Success;
358}
359bool clang::interp::DoBitCastPtr(InterpState &S, CodePtr OpPC,
360 const Pointer &FromPtr, Pointer &ToPtr) {
361 const ASTContext &ASTCtx = S.getASTContext();
362 CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(T: ToPtr.getType());
363
364 return DoBitCastPtr(S, OpPC, FromPtr, ToPtr, Size: ObjectReprChars.getQuantity());
365}
366
367bool clang::interp::DoBitCastPtr(InterpState &S, CodePtr OpPC,
368 const Pointer &FromPtr, Pointer &ToPtr,
369 size_t Size) {
370 assert(FromPtr.isLive());
371 assert(FromPtr.isBlockPointer());
372 assert(ToPtr.isBlockPointer());
373
374 QualType FromType = FromPtr.getFieldDesc()->getDataType(Ctx: S.getASTContext());
375 QualType ToType = ToPtr.getFieldDesc()->getDataType(Ctx: S.getASTContext());
376
377 if (!CheckBitcastType(S, OpPC, T: ToType, /*IsToType=*/true))
378 return false;
379 if (!CheckBitcastType(S, OpPC, T: FromType, /*IsToType=*/false))
380 return false;
381
382 const ASTContext &ASTCtx = S.getASTContext();
383 BitcastBuffer Buffer(Bytes(Size).toBits());
384 readPointerToBuffer(Ctx: S.getContext(), FromPtr, Buffer,
385 /*ReturnOnUninit=*/false);
386
387 // Now read the values out of the buffer again and into ToPtr.
388 Endian TargetEndianness =
389 ASTCtx.getTargetInfo().isLittleEndian() ? Endian::Little : Endian::Big;
390 bool Success = enumeratePointerFields(
391 P: ToPtr, Ctx: S.getContext(), BitsToRead: Buffer.size(),
392 F: [&](const Pointer &P, PrimType T, Bits BitOffset, Bits FullBitWidth,
393 bool PackedBools) -> bool {
394 QualType PtrType = P.getType();
395 if (T == PT_Float) {
396 const auto &Semantics = ASTCtx.getFloatTypeSemantics(T: PtrType);
397 Bits NumBits = Bits(llvm::APFloatBase::getSizeInBits(Sem: Semantics));
398 assert(NumBits.isFullByte());
399 assert(NumBits.getQuantity() <= FullBitWidth.getQuantity());
400 auto M = Buffer.copyBits(BitOffset, BitWidth: NumBits, FullBitWidth,
401 TargetEndianness);
402
403 if (llvm::sys::IsBigEndianHost)
404 swapBytes(M: M.get(), N: NumBits.roundToBytes());
405
406 Floating R = S.allocFloat(Sem: Semantics);
407 Floating::bitcastFromMemory(Buff: M.get(), Sem: Semantics, Result: &R);
408 P.deref<Floating>() = R;
409 P.initialize();
410 return true;
411 }
412
413 Bits BitWidth;
414 if (const FieldDecl *FD = P.getField(); FD && FD->isBitField())
415 BitWidth = Bits(std::min(a: FD->getBitWidthValue(),
416 b: (unsigned)FullBitWidth.getQuantity()));
417 else if (T == PT_Bool && PackedBools)
418 BitWidth = Bits(1);
419 else
420 BitWidth = FullBitWidth;
421
422 // If any of the bits are uninitialized, we need to abort unless the
423 // target type is std::byte or unsigned char.
424 bool Initialized = Buffer.rangeInitialized(Offset: BitOffset, Length: BitWidth);
425 if (!Initialized) {
426 if (!PtrType->isStdByteType() &&
427 !PtrType->isSpecificBuiltinType(K: BuiltinType::UChar) &&
428 !PtrType->isSpecificBuiltinType(K: BuiltinType::Char_U)) {
429 const Expr *E = S.Current->getExpr(PC: OpPC);
430 S.FFDiag(E, DiagId: diag::note_constexpr_bit_cast_indet_dest)
431 << PtrType << S.getLangOpts().CharIsSigned
432 << E->getSourceRange();
433
434 return false;
435 }
436 return true;
437 }
438
439 auto Memory = Buffer.copyBits(BitOffset, BitWidth, FullBitWidth,
440 TargetEndianness);
441 if (llvm::sys::IsBigEndianHost)
442 swapBytes(M: Memory.get(), N: FullBitWidth.roundToBytes());
443
444 BITCAST_TYPE_SWITCH_FIXED_SIZE(T, {
445 if (BitWidth.nonZero())
446 P.deref<T>() = T::bitcastFromMemory(Memory.get(), T::bitWidth())
447 .truncate(BitWidth.getQuantity());
448 else
449 P.deref<T>() = T::zero();
450 });
451 P.initialize();
452 return true;
453 });
454
455 return Success;
456}
457
458using PrimTypeVariant =
459 std::variant<Pointer, FunctionPointer, MemberPointer, FixedPoint,
460 Integral<8, false>, Integral<8, true>, Integral<16, false>,
461 Integral<16, true>, Integral<32, false>, Integral<32, true>,
462 Integral<64, false>, Integral<64, true>, IntegralAP<true>,
463 IntegralAP<false>, Boolean, Floating>;
464
465// NB: This implementation isn't exactly ideal, but:
466// 1) We can't just do a bitcast here since we need to be able to
467// copy pointers.
468// 2) This also needs to handle overlapping regions.
469// 3) We currently have no way of iterating over the fields of a pointer
470// backwards.
471bool clang::interp::DoMemcpy(InterpState &S, CodePtr OpPC,
472 const Pointer &SrcPtr, const Pointer &DestPtr,
473 Bits Size) {
474 assert(SrcPtr.isBlockPointer());
475 assert(DestPtr.isBlockPointer());
476
477 llvm::SmallVector<PrimTypeVariant> Values;
478 enumeratePointerFields(P: SrcPtr, Ctx: S.getContext(), BitsToRead: Size,
479 F: [&](const Pointer &P, PrimType T, Bits BitOffset,
480 Bits FullBitWidth, bool PackedBools) -> bool {
481 TYPE_SWITCH(T, { Values.push_back(P.deref<T>()); });
482 return true;
483 });
484
485 unsigned ValueIndex = 0;
486 enumeratePointerFields(P: DestPtr, Ctx: S.getContext(), BitsToRead: Size,
487 F: [&](const Pointer &P, PrimType T, Bits BitOffset,
488 Bits FullBitWidth, bool PackedBools) -> bool {
489 TYPE_SWITCH(T, {
490 P.deref<T>() = std::get<T>(Values[ValueIndex]);
491 P.initialize();
492 });
493
494 ++ValueIndex;
495 return true;
496 });
497
498 // We should've read all the values into DestPtr.
499 assert(ValueIndex == Values.size());
500
501 return true;
502}
503