Skip to content

Commit ef2a104

Browse files
authored
[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.
1 parent 1559568 commit ef2a104

File tree

14 files changed

+1002
-0
lines changed

14 files changed

+1002
-0
lines changed

clang/lib/AST/ByteCode/Boolean.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ class Boolean final {
8181

8282
Boolean truncate(unsigned TruncBits) const { return *this; }
8383

84+
static Boolean bitcastFromMemory(const std::byte *Buff, unsigned BitWidth) {
85+
// Boolean width is currently always 8 for all supported targets. If this
86+
// changes we need to get the bool width from the target info.
87+
assert(BitWidth == 8);
88+
bool Val = static_cast<bool>(*Buff);
89+
return Boolean(Val);
90+
}
91+
92+
void bitcastToMemory(std::byte *Buff) { std::memcpy(Buff, &V, sizeof(V)); }
93+
8494
void print(llvm::raw_ostream &OS) const { OS << (V ? "true" : "false"); }
8595
std::string toDiagnosticString(const ASTContext &Ctx) const {
8696
std::string NameStr;

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,9 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *CE) {
470470
return this->emitDecayPtr(*FromT, *ToT, CE);
471471
}
472472

473+
case CK_LValueToRValueBitCast:
474+
return this->emitBuiltinBitCast(CE);
475+
473476
case CK_IntegralToBoolean:
474477
case CK_FixedPointToBoolean:
475478
case CK_BooleanToSignedIntegral:
@@ -6426,6 +6429,66 @@ bool Compiler<Emitter>::emitDummyPtr(const DeclTy &D, const Expr *E) {
64266429
return this->emitDecayPtr(PT_Ptr, PT, E);
64276430
return false;
64286431
}
6432+
return true;
6433+
}
6434+
6435+
// This function is constexpr if and only if To, From, and the types of
6436+
// all subobjects of To and From are types T such that...
6437+
// (3.1) - is_union_v<T> is false;
6438+
// (3.2) - is_pointer_v<T> is false;
6439+
// (3.3) - is_member_pointer_v<T> is false;
6440+
// (3.4) - is_volatile_v<T> is false; and
6441+
// (3.5) - T has no non-static data members of reference type
6442+
template <class Emitter>
6443+
bool Compiler<Emitter>::emitBuiltinBitCast(const CastExpr *E) {
6444+
const Expr *SubExpr = E->getSubExpr();
6445+
QualType FromType = SubExpr->getType();
6446+
QualType ToType = E->getType();
6447+
std::optional<PrimType> ToT = classify(ToType);
6448+
6449+
assert(!DiscardResult && "Implement DiscardResult mode for bitcasts.");
6450+
6451+
if (ToType->isNullPtrType()) {
6452+
if (!this->discard(SubExpr))
6453+
return false;
6454+
6455+
return this->emitNullPtr(nullptr, E);
6456+
}
6457+
6458+
if (FromType->isNullPtrType() && ToT) {
6459+
if (!this->discard(SubExpr))
6460+
return false;
6461+
6462+
return visitZeroInitializer(*ToT, ToType, E);
6463+
}
6464+
assert(!ToType->isReferenceType());
6465+
6466+
// Get a pointer to the value-to-cast on the stack.
6467+
if (!this->visit(SubExpr))
6468+
return false;
6469+
6470+
if (!ToT || ToT == PT_Ptr) {
6471+
// Conversion to an array or record type.
6472+
assert(false && "Implement bitcast to pointers.");
6473+
}
6474+
assert(ToT);
6475+
6476+
const llvm::fltSemantics *TargetSemantics = nullptr;
6477+
if (ToT == PT_Float)
6478+
TargetSemantics = &Ctx.getFloatSemantics(ToType);
6479+
6480+
// Conversion to a primitive type. FromType can be another
6481+
// primitive type, or a record/array.
6482+
bool ToTypeIsUChar = (ToType->isSpecificBuiltinType(BuiltinType::UChar) ||
6483+
ToType->isSpecificBuiltinType(BuiltinType::Char_U));
6484+
uint32_t ResultBitWidth = std::max(Ctx.getBitWidth(ToType), 8u);
6485+
6486+
if (!this->emitBitCast(*ToT, ToTypeIsUChar || ToType->isStdByteType(),
6487+
ResultBitWidth, TargetSemantics, E))
6488+
return false;
6489+
6490+
if (DiscardResult)
6491+
return this->emitPop(*ToT, E);
64296492

64306493
return true;
64316494
}

clang/lib/AST/ByteCode/Compiler.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>, bool>,
374374
unsigned collectBaseOffset(const QualType BaseType,
375375
const QualType DerivedType);
376376
bool emitLambdaStaticInvokerBody(const CXXMethodDecl *MD);
377+
bool emitBuiltinBitCast(const CastExpr *E);
377378
bool compileConstructor(const CXXConstructorDecl *Ctor);
378379
bool compileDestructor(const CXXDestructorDecl *Dtor);
379380

clang/lib/AST/ByteCode/Floating.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ class Floating final {
135135
return Floating(APFloat(Sem, API));
136136
}
137137

138+
void bitcastToMemory(std::byte *Buff) {
139+
llvm::APInt API = F.bitcastToAPInt();
140+
llvm::StoreIntToMemory(API, (uint8_t *)Buff, bitWidth() / 8);
141+
}
142+
138143
// === Serialization support ===
139144
size_t bytesToSerialize() const {
140145
return sizeof(llvm::fltSemantics *) +

clang/lib/AST/ByteCode/Integral.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ template <unsigned Bits, bool Signed> class Integral final {
7070
// The primitive representing the integral.
7171
using ReprT = typename Repr<Bits, Signed>::Type;
7272
ReprT V;
73+
static_assert(std::is_trivially_copyable_v<ReprT>);
7374

7475
/// Primitive representing limits.
7576
static const auto Min = std::numeric_limits<ReprT>::min();
@@ -154,6 +155,18 @@ template <unsigned Bits, bool Signed> class Integral final {
154155
return Compare(V, RHS.V);
155156
}
156157

158+
void bitcastToMemory(std::byte *Dest) const {
159+
std::memcpy(Dest, &V, sizeof(V));
160+
}
161+
162+
static Integral bitcastFromMemory(const std::byte *Src, unsigned BitWidth) {
163+
assert(BitWidth == sizeof(ReprT) * 8);
164+
ReprT V;
165+
166+
std::memcpy(&V, Src, sizeof(ReprT));
167+
return Integral(V);
168+
}
169+
157170
std::string toDiagnosticString(const ASTContext &Ctx) const {
158171
std::string NameStr;
159172
llvm::raw_string_ostream OS(NameStr);

clang/lib/AST/ByteCode/IntegralAP.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ template <bool Signed> class IntegralAP final {
171171
return IntegralAP<false>(Copy);
172172
}
173173

174+
void bitcastToMemory(std::byte *Dest) const { assert(false); }
175+
176+
static IntegralAP bitcastFromMemory(const std::byte *Src, unsigned BitWidth) {
177+
return IntegralAP();
178+
}
179+
174180
ComparisonCategoryResult compare(const IntegralAP &RHS) const {
175181
assert(Signed == RHS.isSigned());
176182
assert(bitWidth() == RHS.bitWidth());

clang/lib/AST/ByteCode/Interp.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1574,6 +1574,23 @@ bool CastPointerIntegralAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth) {
15741574
return true;
15751575
}
15761576

1577+
bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits,
1578+
bool TargetIsUCharOrByte) {
1579+
// This is always fine.
1580+
if (!HasIndeterminateBits)
1581+
return true;
1582+
1583+
// Indeterminate bits can only be bitcast to unsigned char or std::byte.
1584+
if (TargetIsUCharOrByte)
1585+
return true;
1586+
1587+
const Expr *E = S.Current->getExpr(OpPC);
1588+
QualType ExprType = E->getType();
1589+
S.FFDiag(E, diag::note_constexpr_bit_cast_indet_dest)
1590+
<< ExprType << S.getLangOpts().CharIsSigned << E->getSourceRange();
1591+
return false;
1592+
}
1593+
15771594
// https://github.com/llvm/llvm-project/issues/102513
15781595
#if defined(_WIN32) && !defined(__clang__) && !defined(NDEBUG)
15791596
#pragma optimize("", off)

clang/lib/AST/ByteCode/Interp.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "Floating.h"
2121
#include "Function.h"
2222
#include "FunctionPointer.h"
23+
#include "InterpBuiltinBitCast.h"
2324
#include "InterpFrame.h"
2425
#include "InterpStack.h"
2526
#include "InterpState.h"
@@ -162,6 +163,8 @@ bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
162163
const CallExpr *CE);
163164
bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T);
164165
bool InvalidShuffleVectorIndex(InterpState &S, CodePtr OpPC, uint32_t Index);
166+
bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits,
167+
bool TargetIsUCharOrByte);
165168

166169
template <typename T>
167170
static bool handleOverflow(InterpState &S, CodePtr OpPC, const T &SrcValue) {
@@ -3039,6 +3042,34 @@ bool CheckNewTypeMismatchArray(InterpState &S, CodePtr OpPC, const Expr *E) {
30393042
return CheckNewTypeMismatch(S, OpPC, E, static_cast<uint64_t>(Size));
30403043
}
30413044
bool InvalidNewDeleteExpr(InterpState &S, CodePtr OpPC, const Expr *E);
3045+
3046+
template <PrimType Name, class T = typename PrimConv<Name>::T>
3047+
inline bool BitCast(InterpState &S, CodePtr OpPC, bool TargetIsUCharOrByte,
3048+
uint32_t ResultBitWidth, const llvm::fltSemantics *Sem) {
3049+
const Pointer &FromPtr = S.Stk.pop<Pointer>();
3050+
3051+
if (!CheckLoad(S, OpPC, FromPtr))
3052+
return false;
3053+
3054+
size_t BuffSize = ResultBitWidth / 8;
3055+
llvm::SmallVector<std::byte> Buff(BuffSize);
3056+
bool HasIndeterminateBits = false;
3057+
3058+
if (!DoBitCast(S, OpPC, FromPtr, Buff.data(), BuffSize, HasIndeterminateBits))
3059+
return false;
3060+
3061+
if (!CheckBitCast(S, OpPC, HasIndeterminateBits, TargetIsUCharOrByte))
3062+
return false;
3063+
3064+
if constexpr (std::is_same_v<T, Floating>) {
3065+
assert(false && "Implement bitcasting to a floating type");
3066+
} else {
3067+
assert(!Sem);
3068+
S.Stk.push<T>(T::bitcastFromMemory(Buff.data(), ResultBitWidth));
3069+
}
3070+
return true;
3071+
}
3072+
30423073
//===----------------------------------------------------------------------===//
30433074
// Read opcode arguments
30443075
//===----------------------------------------------------------------------===//

clang/lib/AST/ByteCode/InterpBuiltin.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "Compiler.h"
1111
#include "EvalEmitter.h"
1212
#include "Interp.h"
13+
#include "InterpBuiltinBitCast.h"
1314
#include "PrimType.h"
1415
#include "clang/AST/OSLog.h"
1516
#include "clang/AST/RecordLayout.h"

0 commit comments

Comments
 (0)