[clang] [Clang][C++20] Implement constexpr std::bit_cast for bit-fields (PR #74775)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jan 4 17:19:12 PST 2024
https://github.com/sethp updated https://github.com/llvm/llvm-project/pull/74775
>From f281d34a51f662c934f158e4770774b0dc3588a2 Mon Sep 17 00:00:00 2001
From: Seth Pellegrino <seth at codecopse.net>
Date: Thu, 7 Dec 2023 08:45:51 -0800
Subject: [PATCH 1/3] [Clang][Sema] Print more static_assert exprs
This change introspects more values involved in a static_assert, and
extends the supported set of operators for introspection to include
binary operator method calls.
It's intended to address the use-case where a small static_assert helper
looks something like this (via `constexpr-builtin-bit-cast.cpp`):
```c++
struct int_splicer {
unsigned x;
unsigned y;
constexpr bool operator==(const int_splicer &other) const {
return other.x == x && other.y == y;
}
};
```
When used like so:
```c++
constexpr int_splicer got{1, 2};
constexpr int_splicer want{3, 4};
static_assert(got == want);
```
Then we'd expect to get the error:
```
Static assertion failed due to requirement 'got == want'
```
And this change adds the helpful note:
```
Expression evaluates to '{1, 2} == {3, 4}'
```
---
clang/lib/Sema/SemaDeclCXX.cpp | 31 ++++++++++++++-----
.../CXX/class/class.compare/class.eq/p3.cpp | 20 ++++++------
.../CXX/class/class.compare/class.rel/p2.cpp | 10 +++---
.../over.match.oper/p9-2a.cpp | 2 +-
clang/test/SemaCXX/static-assert-cxx17.cpp | 2 +-
5 files changed, 40 insertions(+), 25 deletions(-)
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c6218a491aecec..e3d46c3140741b 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -17219,6 +17219,13 @@ static bool ConvertAPValueToString(const APValue &V, QualType T,
OS << "i)";
} break;
+ case APValue::ValueKind::Array:
+ case APValue::ValueKind::Vector:
+ case APValue::ValueKind::Struct: {
+ llvm::raw_svector_ostream OS(Str);
+ V.printPretty(OS, Context, T);
+ } break;
+
default:
return false;
}
@@ -17256,11 +17263,10 @@ static bool UsefulToPrintExpr(const Expr *E) {
/// Try to print more useful information about a failed static_assert
/// with expression \E
void Sema::DiagnoseStaticAssertDetails(const Expr *E) {
- if (const auto *Op = dyn_cast<BinaryOperator>(E);
- Op && Op->getOpcode() != BO_LOr) {
- const Expr *LHS = Op->getLHS()->IgnoreParenImpCasts();
- const Expr *RHS = Op->getRHS()->IgnoreParenImpCasts();
-
+ const auto Diagnose = [&](const Expr *LHS, const Expr *RHS,
+ const llvm::StringRef &OpStr) {
+ LHS = LHS->IgnoreParenImpCasts();
+ RHS = RHS->IgnoreParenImpCasts();
// Ignore comparisons of boolean expressions with a boolean literal.
if ((isa<CXXBoolLiteralExpr>(LHS) && RHS->getType()->isBooleanType()) ||
(isa<CXXBoolLiteralExpr>(RHS) && LHS->getType()->isBooleanType()))
@@ -17287,10 +17293,19 @@ void Sema::DiagnoseStaticAssertDetails(const Expr *E) {
DiagSide[I].ValueString, Context);
}
if (DiagSide[0].Print && DiagSide[1].Print) {
- Diag(Op->getExprLoc(), diag::note_expr_evaluates_to)
- << DiagSide[0].ValueString << Op->getOpcodeStr()
- << DiagSide[1].ValueString << Op->getSourceRange();
+ Diag(E->getExprLoc(), diag::note_expr_evaluates_to)
+ << DiagSide[0].ValueString << OpStr << DiagSide[1].ValueString
+ << E->getSourceRange();
}
+ };
+
+ if (const auto *Op = dyn_cast<BinaryOperator>(E);
+ Op && Op->getOpcode() != BO_LOr) {
+ Diagnose(Op->getLHS(), Op->getRHS(), Op->getOpcodeStr());
+ } else if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(E);
+ Op && Op->isInfixBinaryOp()) {
+ Diagnose(Op->getArg(0), Op->getArg(1),
+ getOperatorSpelling(Op->getOperator()));
}
}
diff --git a/clang/test/CXX/class/class.compare/class.eq/p3.cpp b/clang/test/CXX/class/class.compare/class.eq/p3.cpp
index 04db022fe73021..53c4dda133301b 100644
--- a/clang/test/CXX/class/class.compare/class.eq/p3.cpp
+++ b/clang/test/CXX/class/class.compare/class.eq/p3.cpp
@@ -6,11 +6,11 @@ struct A {
};
static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 4, 5});
-static_assert(A{1, 2, 3, 4, 5} == A{0, 2, 3, 4, 5}); // expected-error {{failed}}
-static_assert(A{1, 2, 3, 4, 5} == A{1, 0, 3, 4, 5}); // expected-error {{failed}}
-static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 0, 4, 5}); // expected-error {{failed}}
-static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 0, 5}); // expected-error {{failed}}
-static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 4, 0}); // expected-error {{failed}}
+static_assert(A{1, 2, 3, 4, 5} == A{0, 2, 3, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}}
+static_assert(A{1, 2, 3, 4, 5} == A{1, 0, 3, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}}
+static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 0, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}}
+static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 0, 5}); // expected-error {{failed}} expected-note {{evaluates to}}
+static_assert(A{1, 2, 3, 4, 5} == A{1, 2, 3, 4, 0}); // expected-error {{failed}} expected-note {{evaluates to}}
struct B {
int a, b[3], c;
@@ -18,8 +18,8 @@ struct B {
};
static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 4, 5});
-static_assert(B{1, 2, 3, 4, 5} == B{0, 2, 3, 4, 5}); // expected-error {{failed}}
-static_assert(B{1, 2, 3, 4, 5} == B{1, 0, 3, 4, 5}); // expected-error {{failed}}
-static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 0, 4, 5}); // expected-error {{failed}}
-static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 0, 5}); // expected-error {{failed}}
-static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 4, 0}); // expected-error {{failed}}
+static_assert(B{1, 2, 3, 4, 5} == B{0, 2, 3, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}}
+static_assert(B{1, 2, 3, 4, 5} == B{1, 0, 3, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}}
+static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 0, 4, 5}); // expected-error {{failed}} expected-note {{evaluates to}}
+static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 0, 5}); // expected-error {{failed}} expected-note {{evaluates to}}
+static_assert(B{1, 2, 3, 4, 5} == B{1, 2, 3, 4, 0}); // expected-error {{failed}} expected-note {{evaluates to}}
diff --git a/clang/test/CXX/class/class.compare/class.rel/p2.cpp b/clang/test/CXX/class/class.compare/class.rel/p2.cpp
index 90115284d2bd02..07501c6a081841 100644
--- a/clang/test/CXX/class/class.compare/class.rel/p2.cpp
+++ b/clang/test/CXX/class/class.compare/class.rel/p2.cpp
@@ -10,15 +10,15 @@ namespace Rel {
friend bool operator>=(const A&, const A&) = default;
};
static_assert(A{0} < A{1});
- static_assert(A{1} < A{1}); // expected-error {{failed}}
+ static_assert(A{1} < A{1}); // expected-error {{failed}} expected-note {{'{1} < {1}'}}
static_assert(A{0} <= A{1});
static_assert(A{1} <= A{1});
- static_assert(A{2} <= A{1}); // expected-error {{failed}}
+ static_assert(A{2} <= A{1}); // expected-error {{failed}} expected-note {{'{2} <= {1}'}}
static_assert(A{1} > A{0});
- static_assert(A{1} > A{1}); // expected-error {{failed}}
+ static_assert(A{1} > A{1}); // expected-error {{failed}} expected-note {{'{1} > {1}'}}
static_assert(A{1} >= A{0});
static_assert(A{1} >= A{1});
- static_assert(A{1} >= A{2}); // expected-error {{failed}}
+ static_assert(A{1} >= A{2}); // expected-error {{failed}} expected-note {{'{1} >= {2}'}}
struct B {
bool operator<=>(B) const = delete; // expected-note 4{{deleted here}} expected-note-re 8{{candidate {{.*}} deleted}}
@@ -49,7 +49,7 @@ namespace NotEqual {
friend bool operator!=(const A&, const A&) = default;
};
static_assert(A{1} != A{2});
- static_assert(A{1} != A{1}); // expected-error {{failed}}
+ static_assert(A{1} != A{1}); // expected-error {{failed}} expected-note {{'{1} != {1}'}}
struct B {
bool operator==(B) const = delete; // expected-note {{deleted here}} expected-note-re 2{{candidate {{.*}} deleted}}
diff --git a/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp
index 95d6a55aee66a1..8f31e8947a768c 100644
--- a/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp
+++ b/clang/test/CXX/over/over.match/over.match.funcs/over.match.oper/p9-2a.cpp
@@ -33,7 +33,7 @@ struct Y {};
constexpr bool operator==(X x, Y) { return x.equal; }
static_assert(X{true} == Y{});
-static_assert(X{false} == Y{}); // expected-error {{failed}}
+static_assert(X{false} == Y{}); // expected-error {{failed}} expected-note{{'{false} == {}'}}
// x == y -> y == x
static_assert(Y{} == X{true});
diff --git a/clang/test/SemaCXX/static-assert-cxx17.cpp b/clang/test/SemaCXX/static-assert-cxx17.cpp
index 41a7b025d0eb75..1d78915aa13e18 100644
--- a/clang/test/SemaCXX/static-assert-cxx17.cpp
+++ b/clang/test/SemaCXX/static-assert-cxx17.cpp
@@ -94,7 +94,7 @@ void foo6() {
// expected-error at -1{{static assertion failed due to requirement '(const X<int> *)nullptr'}}
static_assert(static_cast<const X<typename T::T> *>(nullptr));
// expected-error at -1{{static assertion failed due to requirement 'static_cast<const X<int> *>(nullptr)'}}
- static_assert((const X<typename T::T>[]){} == nullptr);
+ static_assert((const X<typename T::T>[]){} == nullptr); // expected-note{{expression evaluates to '{} == nullptr'}}
// expected-error at -1{{static assertion failed due to requirement '(const X<int>[0]){} == nullptr'}}
static_assert(sizeof(X<decltype(X<typename T::T>().X<typename T::T>::~X())>) == 0);
// expected-error at -1{{static assertion failed due to requirement 'sizeof(X<void>) == 0'}} \
>From f9785e47a5f9954690d8a30a4296cc833d5185b9 Mon Sep 17 00:00:00 2001
From: Seth Pellegrino <seth at codecopse.net>
Date: Thu, 7 Dec 2023 09:29:13 -0800
Subject: [PATCH 2/3] [Clang][C++20] Implement constexpr std::bit_cast for
bit-fields
After this commit, clang permits constructions like:
```c++
struct bits {
unsigned char : 7;
unsigned char flag : 1;
}
static_assert(std::bit_cast<bits>(0x80).flag); // succeeds on little-endian systems
```
This change builds on the prior work in https://reviews.llvm.org/D62825
---
.../include/clang/Basic/DiagnosticASTKinds.td | 8 +-
clang/lib/AST/ExprConstant.cpp | 403 +++++++++++++++---
.../SemaCXX/constexpr-builtin-bit-cast.cpp | 384 ++++++++++++++---
.../SemaTemplate/temp_arg_nontype_cxx20.cpp | 12 +-
4 files changed, 690 insertions(+), 117 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index c81d17ed641084..7020f70f7c1b07 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -316,10 +316,14 @@ def note_constexpr_memcpy_unsupported : Note<
"size to copy (%4) is not a multiple of size of element type %3 (%5)|"
"source is not a contiguous array of at least %4 elements of type %3|"
"destination is not a contiguous array of at least %4 elements of type %3}2">;
+def note_constexpr_bit_cast_bad_bits : Note<
+ "bit_cast source expression (type %5) does not produce a constant value for "
+ "%select{bit|byte}0 [%1] (of {%2%plural{0:|:..0}2}) which are required by "
+ "target type %4 %select{|(subobject %3)}6">;
def note_constexpr_bit_cast_unsupported_type : Note<
"constexpr bit cast involving type %0 is not yet supported">;
-def note_constexpr_bit_cast_unsupported_bitfield : Note<
- "constexpr bit_cast involving bit-field is not yet supported">;
+def note_constexpr_bit_cast_invalid_decl : Note<
+ "bit_cast here %select{from|to}0 invalid declaration %0">;
def note_constexpr_bit_cast_invalid_type : Note<
"bit_cast %select{from|to}0 a %select{|type with a }1"
"%select{union|pointer|member pointer|volatile|reference}2 "
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 986302e1fd225f..356cef552d3544 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -38,7 +38,6 @@
#include "Interp/State.h"
#include "clang/AST/APValue.h"
#include "clang/AST/ASTContext.h"
-#include "clang/AST/ASTDiagnostic.h"
#include "clang/AST/ASTLambda.h"
#include "clang/AST/Attr.h"
#include "clang/AST/CXXInheritance.h"
@@ -49,19 +48,33 @@
#include "clang/AST/OptionalDiagnostic.h"
#include "clang/AST/RecordLayout.h"
#include "clang/AST/StmtVisitor.h"
+#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/Builtins.h"
-#include "clang/Basic/DiagnosticSema.h"
+#include "clang/Basic/DiagnosticAST.h"
#include "clang/Basic/TargetInfo.h"
#include "llvm/ADT/APFixedPoint.h"
+#include "llvm/ADT/APInt.h"
+#include "llvm/ADT/APSInt.h"
#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/Support/Compiler.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/SaveAndRestore.h"
+#include "llvm/Support/SwapByteOrder.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/raw_ostream.h"
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
#include <cstring>
#include <functional>
+#include <iomanip>
+#include <iterator>
#include <optional>
#define DEBUG_TYPE "exprconstant"
@@ -6901,51 +6914,113 @@ bool HandleOperatorDeleteCall(EvalInfo &Info, const CallExpr *E) {
//===----------------------------------------------------------------------===//
namespace {
-class BitCastBuffer {
- // FIXME: We're going to need bit-level granularity when we support
- // bit-fields.
+struct BitCastBuffer {
// FIXME: Its possible under the C++ standard for 'char' to not be 8 bits, but
// we don't support a host or target where that is the case. Still, we should
// use a more generic type in case we ever do.
- SmallVector<std::optional<unsigned char>, 32> Bytes;
-
- static_assert(std::numeric_limits<unsigned char>::digits >= 8,
+ using byte_t = unsigned char;
+ static_assert(std::numeric_limits<byte_t>::digits >= 8,
"Need at least 8 bit unsigned char");
+ SmallVector<byte_t, 32> Bytes;
+ SmallVector<byte_t, 32> Valid;
+
bool TargetIsLittleEndian;
-public:
+ static SmallVector<byte_t> MaskAllSet(size_t Width) {
+ SmallVector<byte_t> M;
+ M.resize(Width);
+ std::fill(M.begin(), M.end(), ~0);
+ return M;
+ }
+
BitCastBuffer(CharUnits Width, bool TargetIsLittleEndian)
- : Bytes(Width.getQuantity()),
+ : Bytes(Width.getQuantity()), Valid(Width.getQuantity()),
TargetIsLittleEndian(TargetIsLittleEndian) {}
[[nodiscard]] bool readObject(CharUnits Offset, CharUnits Width,
- SmallVectorImpl<unsigned char> &Output) const {
- for (CharUnits I = Offset, E = Offset + Width; I != E; ++I) {
- // If a byte of an integer is uninitialized, then the whole integer is
- // uninitialized.
- if (!Bytes[I.getQuantity()])
+ SmallVectorImpl<byte_t> &Output,
+ SmallVectorImpl<byte_t> const &Mask) const {
+ assert(Mask.size() >= static_cast<unsigned>(Width.getQuantity()));
+ assert(Output.size() >= static_cast<unsigned>(Width.getQuantity()));
+ assert(Bytes.size() >=
+ static_cast<unsigned>((Offset + Width).getQuantity()));
+
+ SmallVector<byte_t, 8> RevMask;
+ const SmallVectorImpl<byte_t> &M =
+ (llvm::sys::IsLittleEndianHost != TargetIsLittleEndian)
+ ? [&]() -> const SmallVectorImpl<byte_t> & {
+ auto W = Width.getQuantity();
+ RevMask.resize_for_overwrite(W);
+ std::reverse_copy(Mask.begin(), Mask.begin() + W, RevMask.begin());
+ return RevMask;
+ }()
+ : Mask;
+
+ size_t Index = 0;
+ for (CharUnits I = Offset, E = Offset + Width; I != E; ++I, ++Index) {
+ const auto BufIdx = I.getQuantity();
+ const auto mask = M[Index];
+ // are there any bits in Mask[Index] that are not set in
+ // Valid[BufIdx]? (NB: more bits can be set, that's just
+ // fine)
+ if ((Valid[BufIdx] & M[Index]) != M[Index])
+ // If any bit of an integer is uninitialized, then the
+ // whole integer is uninitialized.
return false;
- Output.push_back(*Bytes[I.getQuantity()]);
+
+ Output[Index] = (Output[Index] & ~mask) | (Bytes[BufIdx] & mask);
}
+
if (llvm::sys::IsLittleEndianHost != TargetIsLittleEndian)
std::reverse(Output.begin(), Output.end());
return true;
}
- void writeObject(CharUnits Offset, SmallVectorImpl<unsigned char> &Input) {
- if (llvm::sys::IsLittleEndianHost != TargetIsLittleEndian)
+ void writeObject(CharUnits Offset, SmallVectorImpl<byte_t> &Input,
+ SmallVectorImpl<byte_t> &Mask) {
+ assert(Mask.size() >= Input.size());
+ assert(Bytes.size() >=
+ static_cast<unsigned>(Offset.getQuantity()) + Input.size());
+
+ // we could promise Input and Mask were `const`, except for this
+ if (llvm::sys::IsLittleEndianHost != TargetIsLittleEndian) {
std::reverse(Input.begin(), Input.end());
+ // we might (will) have more mask bits than input bits
+ std::reverse(Mask.begin(), Mask.begin() + Input.size());
+ }
size_t Index = 0;
- for (unsigned char Byte : Input) {
- assert(!Bytes[Offset.getQuantity() + Index] && "overwriting a byte?");
- Bytes[Offset.getQuantity() + Index] = Byte;
+ size_t BufIdx = Offset.getQuantity();
+ for (byte_t &Byte : Input) {
+ assert((Valid[BufIdx] & Mask[Index]) == 0 && "overwriting data?");
+ Bytes[BufIdx] |= Byte & Mask[Index];
+ Valid[BufIdx] |= Mask[Index];
+ ++BufIdx;
++Index;
}
}
size_t size() { return Bytes.size(); }
+
+ LLVM_DUMP_METHOD void dump() {
+ auto pp = [](std::stringstream &SS, llvm::SmallVectorImpl<byte_t> &V) {
+ bool first = true;
+ for (byte_t v : V) {
+ if (first)
+ first = false;
+ else
+ SS << " ";
+ SS << "0x" << std::hex << std::setw(2) << std::setfill('0')
+ << static_cast<unsigned>(v);
+ }
+ };
+ std::stringstream SS[2];
+ pp(SS[0], Bytes);
+ pp(SS[1], Valid);
+ llvm::dbgs() << "BitCastBuffer{Bytes: [" << SS[0].str() << "], Valid: ["
+ << SS[1].str() << "]}\n";
+ }
};
/// Traverse an APValue to produce an BitCastBuffer, emulating how the current
@@ -6973,7 +7048,7 @@ class APValueToBufferConverter {
if (Ty->isNullPtrType())
return true;
- // Dig through Src to find the byte at SrcOffset.
+ // Dig through Val to find the byte at Offset.
switch (Val.getKind()) {
case APValue::Indeterminate:
case APValue::None:
@@ -7012,6 +7087,9 @@ class APValueToBufferConverter {
bool visitRecord(const APValue &Val, QualType Ty, CharUnits Offset) {
const RecordDecl *RD = Ty->getAsRecordDecl();
+ if (RD->isInvalidDecl()) {
+ return invalidDecl(Ty);
+ }
const ASTRecordLayout &Layout = Info.Ctx.getASTRecordLayout(RD);
// Visit the base classes.
@@ -7028,12 +7106,11 @@ class APValueToBufferConverter {
// Visit the fields.
unsigned FieldIdx = 0;
- for (FieldDecl *FD : RD->fields()) {
- if (FD->isBitField()) {
- Info.FFDiag(BCE->getBeginLoc(),
- diag::note_constexpr_bit_cast_unsupported_bitfield);
- return false;
- }
+ for (auto I = RD->field_begin(), E = RD->field_end(); I != E;
+ I++, FieldIdx++) {
+ FieldDecl *FD = *I;
+ if (FD->isBitField())
+ continue; // see below
uint64_t FieldOffsetBits = Layout.getFieldOffset(FieldIdx);
@@ -7044,7 +7121,72 @@ class APValueToBufferConverter {
QualType FieldTy = FD->getType();
if (!visit(Val.getStructField(FieldIdx), FieldTy, FieldOffset))
return false;
- ++FieldIdx;
+ }
+
+ // Handle bit-fields
+ FieldIdx = 0;
+ for (auto I = RD->field_begin(), E = RD->field_end(); I != E;
+ I++, FieldIdx++) {
+ FieldDecl *FD = *I;
+ if (!FD->isBitField())
+ continue;
+
+ // unnamed bit fields are purely padding
+ if (FD->isUnnamedBitfield())
+ continue;
+
+ auto FieldVal = Val.getStructField(FieldIdx);
+ if (!FieldVal.hasValue())
+ continue;
+
+ uint64_t FieldOffsetBits = Layout.getFieldOffset(FieldIdx);
+ CharUnits BufOffset = Offset;
+ uint64_t BitOffset = FieldOffsetBits;
+
+ unsigned int BitWidth = FD->getBitWidthValue(Info.Ctx);
+
+ CharUnits TypeWidth = Info.Ctx.getTypeSizeInChars(FD->getType());
+ uint64_t TypeWidthBits = Info.Ctx.toBits(TypeWidth);
+ if (BitWidth > TypeWidthBits) {
+ // e.g. `unsigned uint8_t c : 12`
+ // we truncate to CHAR_BIT * sizeof(T)
+ // (the extra bits are padding)
+ BitWidth = TypeWidthBits;
+ }
+ if (FieldOffsetBits >= TypeWidthBits) {
+ // e.g. `uint32_t : 33; uint32_t i : 12`
+ // or `uint16_t : 16; unsigned uint16_t i : 12`
+ BufOffset =
+ BufOffset + CharUnits::fromQuantity(BitOffset / TypeWidthBits) *
+ TypeWidth.getQuantity();
+ BitOffset %= TypeWidthBits;
+ }
+
+ if (Info.Ctx.getTargetInfo().isBigEndian()) {
+ // big endian bits count from MSB to LSB
+ // so a bit-field of width 16 and size 12 will occupy bits [0-11] on a
+ // little endian machine, but [3-15] on a big endian machine
+ BitOffset = TypeWidthBits - (BitOffset + BitWidth);
+ }
+
+ assert(TypeWidth >= Info.Ctx.toCharUnitsFromBits(BitWidth));
+
+ llvm::SmallBitVector MaskBits(Info.Ctx.toBits(TypeWidth));
+ MaskBits.set(BitOffset, BitOffset + BitWidth);
+ uintptr_t Store;
+ ArrayRef<uintptr_t> Ref = MaskBits.getData(Store);
+ SmallVector<uint8_t, 8> Mask(Ref.size() * sizeof(uintptr_t));
+ std::memcpy(Mask.data(), Ref.data(), Mask.size());
+ Mask.truncate(TypeWidth.getQuantity());
+
+ SmallVector<uint8_t, 8> Bytes(TypeWidth.getQuantity());
+
+ APSInt Val = FieldVal.getInt() << BitOffset;
+ assert(Val.getBitWidth() >= BitOffset + BitWidth &&
+ "lost data in APInt -> byte buffer conversion");
+
+ llvm::StoreIntToMemory(Val, &*Bytes.begin(), TypeWidth.getQuantity());
+ Buffer.writeObject(BufOffset, Bytes, Mask);
}
return true;
@@ -7129,8 +7271,9 @@ class APValueToBufferConverter {
}
SmallVector<uint8_t, 8> Bytes(NElts / 8);
+ auto Mask = BitCastBuffer::MaskAllSet(Bytes.size());
llvm::StoreIntToMemory(Res, &*Bytes.begin(), NElts / 8);
- Buffer.writeObject(Offset, Bytes);
+ Buffer.writeObject(Offset, Bytes, Mask);
} else {
// Iterate over each of the elements and write them out to the buffer at
// the appropriate offset.
@@ -7153,8 +7296,9 @@ class APValueToBufferConverter {
}
SmallVector<uint8_t, 8> Bytes(Width / 8);
+ auto Mask = BitCastBuffer::MaskAllSet(Bytes.size());
llvm::StoreIntToMemory(AdjustedVal, &*Bytes.begin(), Width / 8);
- Buffer.writeObject(Offset, Bytes);
+ Buffer.writeObject(Offset, Bytes, Mask);
return true;
}
@@ -7163,6 +7307,12 @@ class APValueToBufferConverter {
return visitInt(AsInt, Ty, Offset);
}
+ bool invalidDecl(QualType Ty) {
+ Info.FFDiag(BCE->getBeginLoc(), diag::note_constexpr_bit_cast_invalid_decl)
+ << /* checking dest */ false << Ty;
+ return false;
+ }
+
public:
static std::optional<BitCastBuffer>
convert(EvalInfo &Info, const APValue &Src, const CastExpr *BCE) {
@@ -7194,6 +7344,12 @@ class BufferToAPValueConverter {
return std::nullopt;
}
+ std::nullopt_t invalidDecl(QualType Ty) {
+ Info.FFDiag(BCE->getBeginLoc(), diag::note_constexpr_bit_cast_invalid_decl)
+ << /* checking dest */ true << Ty;
+ return std::nullopt;
+ }
+
std::nullopt_t unrepresentableValue(QualType Ty, const APSInt &Val) {
Info.FFDiag(BCE->getBeginLoc(),
diag::note_constexpr_bit_cast_unrepresentable_value)
@@ -7201,6 +7357,75 @@ class BufferToAPValueConverter {
return std::nullopt;
}
+ std::nullopt_t badBits(QualType Ty, CharUnits Offset,
+ SmallVectorImpl<BitCastBuffer::byte_t> &M) {
+ Info.FFDiag(BCE->getExprLoc(), diag::note_constexpr_bit_cast_indet_dest, 1)
+ << Ty << Info.Ctx.getLangOpts().CharIsSigned;
+ uint64_t BitWidth = Info.Ctx.getTypeSize(BCE->getType());
+ uint64_t ByteWidth = Info.Ctx.toCharUnitsFromBits(BitWidth).getQuantity();
+ assert(ByteWidth == Buffer.Valid.size_in_bytes());
+
+ APInt Valid(BitWidth, 0);
+ llvm::LoadIntFromMemory(Valid, Buffer.Valid.begin(), ByteWidth);
+ APInt Mask(BitWidth, 0);
+ llvm::LoadIntFromMemory(Mask, M.begin(), M.size_in_bytes());
+
+ Mask = Mask.zext(Valid.getBitWidth());
+ Mask <<= Info.Ctx.toBits(Offset);
+
+ auto ByteAligned = true;
+
+ APInt Missing = (~Valid & Mask);
+ assert(!Missing.isZero() && "bad bits called with no bad bits?");
+ llvm::SmallVector<std::pair<size_t, size_t>> MissingBitRanges;
+ int NextBit = 0;
+ while (!Missing.isZero()) {
+ APInt Last(Missing);
+ int N = Missing.countr_zero();
+
+ Missing.lshrInPlace(N);
+ auto M = Missing.countr_one();
+
+ MissingBitRanges.push_back({NextBit + N, NextBit + N + M});
+
+ Missing.lshrInPlace(M);
+ NextBit += N;
+ NextBit += M;
+ ByteAligned &= N % Info.Ctx.getCharWidth() == 0;
+ ByteAligned &= M % Info.Ctx.getCharWidth() == 0;
+ }
+
+ llvm::SmallString<32> RangesStr;
+ llvm::raw_svector_ostream OS(RangesStr);
+ bool First = true;
+ for (auto [Start, End] : MissingBitRanges) {
+ if (!First)
+ OS << " ";
+ else
+ First = false;
+ if (ByteAligned) {
+ Start /= Info.Ctx.getCharWidth();
+ End /= Info.Ctx.getCharWidth();
+ }
+ size_t Len = End - Start;
+ if (Len > 1) {
+ OS << Start << "-" << End - 1;
+ } else {
+ OS << Start;
+ }
+ }
+
+ assert(RangesStr.size() > 0);
+ auto LastIdx = (ByteAligned ? ByteWidth : BitWidth) - 1;
+ bool IsForSubobject =
+ BCE->getType().getCanonicalType() != Ty.getCanonicalType();
+ Info.Note(BCE->getSubExpr()->getExprLoc(),
+ diag::note_constexpr_bit_cast_bad_bits)
+ << ByteAligned << RangesStr << LastIdx << Ty << BCE->getType()
+ << BCE->getSubExpr()->getType() << IsForSubobject;
+ return std::nullopt;
+ }
+
std::optional<APValue> visit(const BuiltinType *T, CharUnits Offset,
const EnumType *EnumSugar = nullptr) {
if (T->isNullPtrType()) {
@@ -7225,8 +7450,10 @@ class BufferToAPValueConverter {
SizeOf = NumBytes;
}
- SmallVector<uint8_t, 8> Bytes;
- if (!Buffer.readObject(Offset, SizeOf, Bytes)) {
+ SmallVector<uint8_t, 8> Bytes,
+ Mask = BitCastBuffer::MaskAllSet(SizeOf.getQuantity());
+ Bytes.resize_for_overwrite(SizeOf.getQuantity());
+ if (!Buffer.readObject(Offset, SizeOf, Bytes, Mask)) {
// If this is std::byte or unsigned char, then its okay to store an
// indeterminate value.
bool IsStdByte = EnumSugar && EnumSugar->isStdByteType();
@@ -7235,10 +7462,7 @@ class BufferToAPValueConverter {
T->isSpecificBuiltinType(BuiltinType::Char_U));
if (!IsStdByte && !IsUChar) {
QualType DisplayType(EnumSugar ? (const Type *)EnumSugar : T, 0);
- Info.FFDiag(BCE->getExprLoc(),
- diag::note_constexpr_bit_cast_indet_dest)
- << DisplayType << Info.Ctx.getLangOpts().CharIsSigned;
- return std::nullopt;
+ return badBits(DisplayType, Offset, Mask);
}
return APValue::IndeterminateValue();
@@ -7272,6 +7496,9 @@ class BufferToAPValueConverter {
std::optional<APValue> visit(const RecordType *RTy, CharUnits Offset) {
const RecordDecl *RD = RTy->getAsRecordDecl();
+ if (RD->isInvalidDecl()) {
+ return invalidDecl(QualType(RD->getTypeForDecl(), 0));
+ }
const ASTRecordLayout &Layout = Info.Ctx.getASTRecordLayout(RD);
unsigned NumBases = 0;
@@ -7300,14 +7527,11 @@ class BufferToAPValueConverter {
// Visit the fields.
unsigned FieldIdx = 0;
- for (FieldDecl *FD : RD->fields()) {
- // FIXME: We don't currently support bit-fields. A lot of the logic for
- // this is in CodeGen, so we need to factor it around.
- if (FD->isBitField()) {
- Info.FFDiag(BCE->getBeginLoc(),
- diag::note_constexpr_bit_cast_unsupported_bitfield);
- return std::nullopt;
- }
+ for (auto I = RD->field_begin(), E = RD->field_end(); I != E;
+ I++, FieldIdx++) {
+ FieldDecl *FD = *I;
+ if (FD->isBitField())
+ continue; // see below
uint64_t FieldOffsetBits = Layout.getFieldOffset(FieldIdx);
assert(FieldOffsetBits % Info.Ctx.getCharWidth() == 0);
@@ -7320,7 +7544,86 @@ class BufferToAPValueConverter {
if (!SubObj)
return std::nullopt;
ResultVal.getStructField(FieldIdx) = *SubObj;
- ++FieldIdx;
+ }
+
+ // Handle bit-fields
+ FieldIdx = 0;
+ for (auto I = RD->field_begin(), E = RD->field_end(); I != E;
+ I++, FieldIdx++) {
+ FieldDecl *FD = *I;
+ if (!FD->isBitField())
+ continue;
+
+ // unnamed bit fields are purely padding
+ if (FD->isUnnamedBitfield())
+ continue;
+
+ uint64_t FieldOffsetBits = Layout.getFieldOffset(FieldIdx);
+ CharUnits BufOffset = Offset;
+ uint64_t BitOffset = FieldOffsetBits;
+
+ unsigned int BitWidth = FD->getBitWidthValue(Info.Ctx);
+
+ CharUnits TypeWidth = Info.Ctx.getTypeSizeInChars(FD->getType());
+ uint64_t TypeWidthBits = Info.Ctx.toBits(TypeWidth);
+ if (BitWidth > TypeWidthBits) {
+ // e.g. `unsigned uint8_t c : 12`
+ // we truncate to CHAR_BIT * sizeof(T)
+ // (the extra bits are padding)
+ BitWidth = TypeWidthBits;
+ }
+ if (FieldOffsetBits >= TypeWidthBits) {
+ // e.g. `uint32_t : 33; uint32_t i : 12`
+ // or `uint16_t : 16; unsigned uint16_t i : 12`
+ BufOffset =
+ BufOffset + CharUnits::fromQuantity(BitOffset / TypeWidthBits) *
+ TypeWidth.getQuantity();
+ BitOffset %= TypeWidthBits;
+ }
+
+ if (Info.Ctx.getTargetInfo().isBigEndian()) {
+ // big endian bits count from MSB to LSB
+ // so a bit-field of width 16 and size 12 will occupy bits [0-11] on a
+ // little endian machine, but [3-15] on a big endian machine
+ BitOffset = TypeWidthBits - (BitOffset + BitWidth);
+ }
+
+ assert(TypeWidth >= Info.Ctx.toCharUnitsFromBits(BitWidth));
+
+ llvm::SmallBitVector MaskBits(Info.Ctx.toBits(TypeWidth));
+ MaskBits.set(BitOffset, BitOffset + BitWidth);
+ uintptr_t Store;
+ ArrayRef<uintptr_t> BitRef = MaskBits.getData(Store);
+ SmallVector<uint8_t, 8> Mask(BitRef.size() * sizeof(uintptr_t));
+ std::memcpy(Mask.data(), BitRef.data(), Mask.size());
+ Mask.truncate(TypeWidth.getQuantity());
+
+ SmallVector<uint8_t, 8> Bytes(TypeWidth.getQuantity());
+ if (!Buffer.readObject(BufOffset, TypeWidth, Bytes, Mask)) {
+ const Type *T = FD->getType().getCanonicalType().getTypePtr();
+ const EnumType *EnumSugar = dyn_cast<EnumType>(T);
+ // If this is std::byte or unsigned char, then its okay to store an
+ // indeterminate value.
+ bool IsStdByte = EnumSugar && EnumSugar->isStdByteType();
+ bool IsUChar =
+ !EnumSugar && (T->isSpecificBuiltinType(BuiltinType::UChar) ||
+ T->isSpecificBuiltinType(BuiltinType::Char_U));
+ if (!IsStdByte && !IsUChar) {
+ QualType DisplayType(EnumSugar ? (const Type *)EnumSugar : T, 0);
+ return badBits(DisplayType, BufOffset, Mask);
+ }
+ ResultVal.getStructField(FieldIdx) = APValue::IndeterminateValue();
+ } else {
+ APSInt Val(Info.Ctx.toBits(TypeWidth), true);
+ llvm::LoadIntFromMemory(Val, &*Bytes.begin(), TypeWidth.getQuantity());
+
+ Val >>= BitOffset;
+ Val = Val.trunc(BitWidth);
+ Val.setIsSigned(FD->getType()->isSignedIntegerOrEnumerationType());
+ Val = Val.extend(Info.Ctx.toBits(TypeWidth));
+
+ ResultVal.getStructField(FieldIdx) = APValue(Val);
+ }
}
return ResultVal;
@@ -7394,9 +7697,11 @@ class BufferToAPValueConverter {
// actually need to be accessed.
bool BigEndian = Info.Ctx.getTargetInfo().isBigEndian();
- SmallVector<uint8_t, 8> Bytes;
- Bytes.reserve(NElts / 8);
- if (!Buffer.readObject(Offset, CharUnits::fromQuantity(NElts / 8), Bytes))
+ size_t Width = NElts / 8;
+ SmallVector<uint8_t, 8> Bytes, Mask = BitCastBuffer::MaskAllSet(Width);
+ Bytes.resize_for_overwrite(Width);
+ if (!Buffer.readObject(Offset, CharUnits::fromQuantity(Width), Bytes,
+ Mask))
return std::nullopt;
APSInt SValInt(NElts, true);
diff --git a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp
index c5b8032f40b131..29d046e2def3db 100644
--- a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp
+++ b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp
@@ -23,28 +23,24 @@ static_assert(sizeof(long long) == 8);
template <class To, class From>
constexpr To bit_cast(const From &from) {
static_assert(sizeof(To) == sizeof(From));
- // expected-note at +9 {{cannot be represented in type 'bool'}}
-#ifdef __x86_64
- // expected-note at +7 {{or 'std::byte'; '__int128' is invalid}}
-#endif
-#ifdef __CHAR_UNSIGNED__
- // expected-note at +4 2 {{indeterminate value can only initialize an object of type 'unsigned char', 'char', or 'std::byte'; 'signed char' is invalid}}
-#else
- // expected-note at +2 2 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'signed char' is invalid}}
-#endif
return __builtin_bit_cast(To, from);
}
template <class Intermediate, class Init>
-constexpr bool round_trip(const Init &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));
+}
+
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));
+ 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));
}
void test_array() {
@@ -73,8 +69,8 @@ void test_record() {
? 0x0C05FEFE
: 0xCAFEBABE));
- static_assert(round_trip<unsigned long long>(splice));
- static_assert(round_trip<long long>(splice));
+ static_assert(round_trip<unsigned long long>(splice) == splice);
+ static_assert(round_trip<long long>(splice) == splice);
struct base2 {
};
@@ -98,7 +94,7 @@ void test_record() {
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));
+ static_assert(round_trip<tuple4>(b) == b);
}
void test_partially_initialized() {
@@ -115,33 +111,295 @@ void test_partially_initialized() {
static_assert(sizeof(pad) == sizeof(no_pad));
+ constexpr auto cast = [](const pad& from) constexpr {
+ // expected-note at +6 2 {{bit_cast source expression (type 'const pad') does not produce a constant value for byte [1] (of {7..0}) which are required by target type 'no_pad' (subobject 'signed char')}}
+ #ifdef __CHAR_UNSIGNED__
+ // expected-note at +4 2 {{indeterminate value can only initialize an object of type 'unsigned char', 'char', or 'std::byte'; 'signed char' is invalid}}
+ #else
+ // expected-note at +2 2 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'signed char' is invalid}}
+ #endif
+ return __builtin_bit_cast(no_pad, from);
+ };
+
constexpr pad pir{4, 4};
// expected-error at +2 {{constexpr variable 'piw' must be initialized by a constant expression}}
- // expected-note at +1 {{in call to 'bit_cast<no_pad, pad>(pir)'}}
- constexpr int piw = bit_cast<no_pad>(pir).x;
+ // expected-note at +1 {{in call}}
+ constexpr int piw = cast(pir).x;
// expected-error at +2 {{constexpr variable 'bad' must be initialized by a constant expression}}
- // expected-note at +1 {{in call to 'bit_cast<no_pad, pad>(pir)'}}
- constexpr no_pad bad = bit_cast<no_pad>(pir);
+ // expected-note at +1 {{in call}}
+ constexpr no_pad bad = cast(pir);
constexpr pad fine = bit_cast<pad>(no_pad{1, 2, 3, 4, 5});
static_assert(fine.x == 1 && fine.y == 5);
}
-void no_bitfields() {
- // FIXME!
+namespace std {
+enum byte : unsigned char {};
+} // namespace std
+
+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;
+}
+
+void test_bitfields() {
+ using uint16_t = unsigned __INT16_TYPE__;
+ {
+ struct Q {
+ // cf. CGBitFieldInfo
+ // on a little-endian machine the bits "[count from] the
+ // least-significant-bit."
+ // so, by leaving a bit unused, we truncate the value's MSB.
+
+ // however, on a big-endian machine we "imagine the bits
+ // counting from the most-significant-bit", so we truncate
+ // the LSB here.
+ uint16_t q : 15;
+ };
+ constexpr unsigned char bits[2] = {0xf3, 0xef};
+ constexpr Q q = bit_cast<Q>(bits);
+ static_assert(bit_cast<uint16_t>(bits) == (LITTLE_END
+ ? 0xeff3
+ : 0xf3ef),
+ "bit-field casting ought to match \"whole\"-field casting");
+ static_assert(q.q == (LITTLE_END ? 0x6ff3 : (0xf3ee >> 1)));
+ }
+
struct S {
- unsigned char x : 8;
+ // little endian:
+ // MSB .... .... LSB
+ // |y| |x|
+ //
+ // big endian
+ // MSB .... .... LSB
+ // |x| |y|
+
+ unsigned char x : 4;
+ unsigned char y : 4;
+
+ constexpr bool operator==(S const &other) const {
+ return x == other.x && y == other.y;
+ }
};
- struct G {
- unsigned char x : 8;
+ constexpr S s{0xa, 0xb};
+ static_assert(bit_cast<bits<8>>(s) == (LITTLE_END ? 0xba : 0xab));
+ static_assert(bit_cast<bits<7>>(s) == (LITTLE_END
+ ? 0xba & 0x7f
+ : (0xab & 0xfe) >> 1));
+
+ static_assert(round_trip<bits<8>>(s) == s);
+
+ struct R {
+ unsigned int r : 31;
+ unsigned int : 0;
+ unsigned int : 32;
+ constexpr bool operator==(R const &other) const {
+ return r == other.r;
+ }
};
+ using T = bits<31, signed long long>;
- constexpr S s{0};
- // expected-error at +2 {{constexpr variable 'g' must be initialized by a constant expression}}
- // expected-note at +1 {{constexpr bit_cast involving bit-field is not yet supported}}
- constexpr G g = __builtin_bit_cast(G, s);
+ constexpr R r{0x4ac0ffee};
+ constexpr T t = bit_cast<T>(r);
+ static_assert(t == ((0xFFFFFFFF8 << 28) | 0x4ac0ffee)); // sign extension
+
+ static_assert(round_trip<T>(r) == r);
+ static_assert(round_trip<R>(t) == t);
+
+ struct U {
+ // expected-warning at +1 {{exceeds the width of its type}}
+ unsigned __INT32_TYPE__ trunc : 33;
+ unsigned __INT32_TYPE__ u : 31;
+ constexpr bool operator==(U const &other) const {
+ return trunc == other.trunc && u == other.u;
+ }
+ };
+ struct V {
+ unsigned __INT64_TYPE__ notrunc : 32;
+ unsigned __INT64_TYPE__ : 1;
+ unsigned __INT64_TYPE__ v : 31;
+ constexpr bool operator==(V const &other) const {
+ return notrunc == other.notrunc && v == other.v;
+ }
+ };
+
+ constexpr U u{static_cast<unsigned int>(~0), 0x4ac0ffee};
+ constexpr V v = bit_cast<V>(u);
+ static_assert(v.v == 0x4ac0ffee);
+
+ {
+ #define MSG "a constexpr ought to produce padding bits from padding bits"
+ static_assert(round_trip<V>(u) == u, MSG);
+ static_assert(round_trip<U>(v) == v, MSG);
+
+ constexpr auto w = bit_cast<bits<12, unsigned long, 33>>(u);
+ static_assert(w == (LITTLE_END
+ ? 0x4ac0ffee & 0xFFF
+ : (0x4ac0ffee & (0xFFF << (31 - 12))) >> (31-12)
+ ), MSG);
+ #undef MSG
+ }
+
+ // nested structures
+ {
+ struct J {
+ struct {
+ uint16_t k : 12;
+ } K;
+ struct {
+ uint16_t l : 4;
+ } L;
+ };
+
+ static_assert(sizeof(J) == 4);
+ constexpr J j = bit_cast<J>(0x8c0ffee5);
+
+ static_assert(j.K.k == (LITTLE_END ? 0xee5 : 0x8c0));
+ static_assert(j.L.l == 0xf /* yay symmetry */);
+ static_assert(bit_cast<bits<4, uint16_t, 16>>(j) == 0xf);
+ struct N {
+ bits<12, uint16_t> k;
+ uint16_t : 16;
+ };
+ static_assert(bit_cast<N>(j).k == j.K.k);
+
+ struct M {
+ bits<4, uint16_t, 0> m[2];
+ constexpr bool operator==(const M& rhs) const {
+ return m[0] == rhs.m[0] && m[1] == rhs.m[1];
+ };
+ };
+ #if LITTLE_END == 1
+ constexpr uint16_t want[2] = {0x5, 0xf};
+ #else
+ constexpr uint16_t want[2] = {0x8000, 0xf000};
+ #endif
+
+ static_assert(bit_cast<M>(j) == bit_cast<M>(want));
+ }
+
+ // enums
+ {
+ // ensure we're packed into the top 2 bits
+ constexpr int pad = LITTLE_END ? 6 : 0;
+ struct X
+ {
+ char : pad;
+ enum class direction: char { left, right, up, down } direction : 2;
+ };
+
+ constexpr X x = { X::direction::down };
+ static_assert(bit_cast<bits<2, signed char, pad>>(x) == -1);
+ static_assert(bit_cast<bits<2, unsigned char, pad>>(x) == 3);
+ static_assert(
+ bit_cast<X>((unsigned char)0x40).direction == X::direction::right);
+ }
+}
+
+template<int N>
+struct bytebuf {
+ using size_t = int;
+ unsigned char bytes[N];
+
+ constexpr unsigned char &operator[](size_t index) {
+ if (index < N)
+ return bytes[index];
+ }
+};
+
+void bitfield_indeterminate() {
+ struct BF { unsigned char z : 2; };
+ enum byte : unsigned char {};
+
+ constexpr BF bf = {0x3};
+ static_assert(bit_cast<bits<2>>(bf).bits == bf.z);
+
+ // expected-error at +1 {{not an integral constant expression}}
+ static_assert(bit_cast<unsigned char>(bf));
+ /// FIXME the above doesn't get any helpful notes, but the below does
+#if LITTLE_END == 1
+ // expected-note at +6 {{bit [2-7]}}
+#else
+ // expected-note at +4 {{bit [0-5]}}
+#endif
+ // expected-note at +2 {{indeterminate}}
+ // expected-error at +1 {{not an integral constant expression}}
+ static_assert(__builtin_bit_cast(byte, bf));
+
+ struct M {
+ // expected-note at +1 {{subobject declared here}}
+ unsigned char mem[sizeof(BF)];
+ };
+ // expected-error at +3 {{initialized by a constant expression}}
+ // zzexpected-note at +2 {{bad bits}}
+ // expected-note at +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<bytebuf<4>>(B);
+ };
+
+ static_assert(f()[0] + f()[1] + f()[2] == 0xc0 + 0xff + 0xee);
+ {
+ // expected-error at +2 {{initialized by a constant expression}}
+ // expected-note at +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 at +2 {{initialized by a constant expression}}
+ // expected-note at +1 {{read of uninitialized object is not allowed in a constant expression}}
+ constexpr auto _bad = g().b2;
+ }
+}
+
+void bitfield_unsupported() {
+ // if a future standard requires more types to be permitted in the
+ // declaration of a bit-field, then this test will hopefully indicate
+ // that there's work to be done on __builtin_bit_cast.
+ struct U {
+ // expected-error at +1 {{bit-field 'f' has non-integral type}}
+ bool f[8] : 8;
+ };
+
+ // this next bit is speculative: if the above _were_ a valid definition,
+ // then the below might also be a reasonable interpretation of its
+ // semantics, but the current implementation of __builtin_bit_cast will
+ // fail
+
+ // expected-note at +3 {{invalid declaration}} FIXME should we instead bail out in Sema?
+ // expected-note at +2 {{declared here}}
+ // expected-error at +1 {{initialized by a constant expression}}
+ constexpr U u = __builtin_bit_cast(U, (char)0b1010'0101);
+ static_assert(U.f[0] && U.f[2] && U.f[4] && U.f[8]);
+ // expected-note at +2 {{not a constant expression}}
+ // expected-error at +1 {{not an integral constant expression}}
+ static_assert(__builtin_bit_cast(bits<8>, u) == 0xA5);
}
void array_members() {
@@ -165,8 +423,8 @@ void array_members() {
constexpr G g = bit_cast<G>(s);
static_assert(g.a == 1 && g.b == 2 && g.c == 3);
- static_assert(round_trip<G>(s));
- static_assert(round_trip<S>(g));
+ static_assert(check_round_trip<G>(s));
+ static_assert(check_round_trip<S>(g));
}
void bad_types() {
@@ -229,6 +487,7 @@ void test_array_fill() {
typedef decltype(nullptr) nullptr_t;
+// expected-note at +7 {{byte [0-7]}}
#ifdef __CHAR_UNSIGNED__
// expected-note at +5 {{indeterminate value can only initialize an object of type 'unsigned char', 'char', or 'std::byte'; 'unsigned long' is invalid}}
#else
@@ -350,10 +609,6 @@ constexpr A two() {
}
constexpr short good_two = two().c + two().s;
-namespace std {
-enum byte : unsigned char {};
-}
-
enum my_byte : unsigned char {};
struct pad {
@@ -364,16 +619,18 @@ struct pad {
constexpr int ok_byte = (__builtin_bit_cast(std::byte[8], pad{1, 2}), 0);
constexpr int ok_uchar = (__builtin_bit_cast(unsigned char[8], pad{1, 2}), 0);
+// expected-note at +7 {{bit_cast source expression (type 'pad') does not produce a constant value for byte [1] (of {7..0}) which are required by target type 'my_byte[8]' (subobject 'my_byte')}}
#ifdef __CHAR_UNSIGNED__
-// expected-note at +5 {{indeterminate value can only initialize an object of type 'unsigned char', 'char', or 'std::byte'; 'my_byte' is invalid}}}}
+// expected-note at +5 {{indeterminate value can only initialize an object of type 'unsigned char', 'char', or 'std::byte'; 'my_byte' is invalid}}
#else
// expected-note at +3 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'my_byte' is invalid}}
#endif
// expected-error at +1 {{constexpr variable 'bad_my_byte' must be initialized by a constant expression}}
constexpr int bad_my_byte = (__builtin_bit_cast(my_byte[8], pad{1, 2}), 0);
#ifndef __CHAR_UNSIGNED__
-// expected-error at +3 {{constexpr variable 'bad_char' must be initialized by a constant expression}}
-// expected-note at +2 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'char' is invalid}}
+// expected-note at +4 {{bit_cast source expression (type 'pad') does not produce a constant value for byte [1] (of {7..0}) which are required by target type 'char[8]' (subobject 'char')}}
+// expected-note at +3 {{indeterminate value can only initialize an object of type 'unsigned char' or 'std::byte'; 'char' is invalid}}
+// expected-error at +2 {{constexpr variable 'bad_char' must be initialized by a constant expression}}
#endif
constexpr int bad_char = (__builtin_bit_cast(char[8], pad{1, 2}), 0);
@@ -404,19 +661,22 @@ constexpr unsigned char identity3b = __builtin_bit_cast(unsigned char, identity3
namespace test_bool {
-constexpr bool test_bad_bool = bit_cast<bool>('A'); // expected-error {{must be initialized by a constant expression}} expected-note{{in call}}
+// expected-note at +1 {{cannot be represented in type 'bool'}}
+constexpr bool test_bad_bool = __builtin_bit_cast(bool, 'A'); // expected-error {{must be initialized by a constant expression}}
-static_assert(round_trip<signed char>(true), "");
-static_assert(round_trip<unsigned char>(false), "");
-static_assert(round_trip<bool>(false), "");
+static_assert(check_round_trip<signed char>(true));
+static_assert(check_round_trip<unsigned char>(false));
+static_assert(check_round_trip<bool>(false));
-static_assert(round_trip<bool>((char)0), "");
-static_assert(round_trip<bool>((char)1), "");
+static_assert(check_round_trip<bool>((char)0));
+static_assert(check_round_trip<bool>((char)1));
}
namespace test_long_double {
#ifdef __x86_64
-constexpr __int128_t test_cast_to_int128 = bit_cast<__int128_t>((long double)0); // expected-error{{must be initialized by a constant expression}} expected-note{{in call}}
+// expected-note at +2 {{byte [10-15]}}
+// expected-note at +1 {{or 'std::byte'; '__int128' is invalid}}
+constexpr __int128_t test_cast_to_int128 = __builtin_bit_cast(__int128_t, (long double)0); // expected-error{{must be initialized by a constant expression}}
constexpr long double ld = 3.1425926539;
@@ -424,9 +684,9 @@ struct bytes {
unsigned char d[16];
};
-static_assert(round_trip<bytes>(ld), "");
+static_assert(check_round_trip<bytes>(ld));
-static_assert(round_trip<long double>(10.0L));
+static_assert(check_round_trip<long double>(10.0L));
constexpr bool f(bool read_uninit) {
bytes b = bit_cast<bytes>(ld);
@@ -445,8 +705,8 @@ constexpr bool f(bool read_uninit) {
return true;
}
-static_assert(f(/*read_uninit=*/false), "");
-static_assert(f(/*read_uninit=*/true), ""); // expected-error{{static assertion expression is not an integral constant expression}} expected-note{{in call to 'f(true)'}}
+static_assert(f(/*read_uninit=*/false));
+static_assert(f(/*read_uninit=*/true)); // expected-error{{static assertion expression is not an integral constant expression}} expected-note{{in call to 'f(true)'}}
constexpr bytes ld539 = {
0x0, 0x0, 0x0, 0x0,
@@ -457,7 +717,7 @@ constexpr bytes ld539 = {
constexpr long double fivehundredandthirtynine = 539.0;
-static_assert(bit_cast<long double>(ld539) == fivehundredandthirtynine, "");
+static_assert(bit_cast<long double>(ld539) == fivehundredandthirtynine);
#else
static_assert(round_trip<__int128_t>(34.0L));
@@ -473,10 +733,10 @@ constexpr uint2 test_vector = { 0x0C05FEFE, 0xCAFEBABE };
static_assert(bit_cast<unsigned long long>(test_vector) == (LITTLE_END
? 0xCAFEBABE0C05FEFE
- : 0x0C05FEFECAFEBABE), "");
+ : 0x0C05FEFECAFEBABE));
-static_assert(round_trip<uint2>(0xCAFEBABE0C05FEFEULL), "");
-static_assert(round_trip<byte8>(0xCAFEBABE0C05FEFEULL), "");
+static_assert(check_round_trip<uint2>(0xCAFEBABE0C05FEFEULL));
+static_assert(check_round_trip<byte8>(0xCAFEBABE0C05FEFEULL));
typedef bool bool8 __attribute__((ext_vector_type(8)));
typedef bool bool9 __attribute__((ext_vector_type(9)));
@@ -485,16 +745,16 @@ typedef bool bool17 __attribute__((ext_vector_type(17)));
typedef bool bool32 __attribute__((ext_vector_type(32)));
typedef bool bool128 __attribute__((ext_vector_type(128)));
-static_assert(bit_cast<unsigned char>(bool8{1,0,1,0,1,0,1,0}) == (LITTLE_END ? 0x55 : 0xAA), "");
-static_assert(round_trip<bool8>(static_cast<unsigned char>(0)), "");
-static_assert(round_trip<bool8>(static_cast<unsigned char>(1)), "");
-static_assert(round_trip<bool8>(static_cast<unsigned char>(0x55)), "");
+static_assert(bit_cast<unsigned char>(bool8{1,0,1,0,1,0,1,0}) == (LITTLE_END ? 0x55 : 0xAA));
+static_assert(check_round_trip<bool8>(static_cast<unsigned char>(0)));
+static_assert(check_round_trip<bool8>(static_cast<unsigned char>(1)));
+static_assert(check_round_trip<bool8>(static_cast<unsigned char>(0x55)));
-static_assert(bit_cast<unsigned short>(bool16{1,1,1,1,1,0,0,0, 1,1,1,1,0,1,0,0}) == (LITTLE_END ? 0x2F1F : 0xF8F4), "");
+static_assert(bit_cast<unsigned short>(bool16{1,1,1,1,1,0,0,0, 1,1,1,1,0,1,0,0}) == (LITTLE_END ? 0x2F1F : 0xF8F4));
-static_assert(round_trip<bool16>(static_cast<short>(0xCAFE)), "");
-static_assert(round_trip<bool32>(static_cast<int>(0xCAFEBABE)), "");
-static_assert(round_trip<bool128>(static_cast<__int128_t>(0xCAFEBABE0C05FEFEULL)), "");
+static_assert(check_round_trip<bool16>(static_cast<short>(0xCAFE)));
+static_assert(check_round_trip<bool32>(static_cast<int>(0xCAFEBABE)));
+static_assert(check_round_trip<bool128>(static_cast<__int128_t>(0xCAFEBABE0C05FEFEULL)));
// expected-error at +2 {{constexpr variable 'bad_bool9_to_short' must be initialized by a constant expression}}
// expected-note at +1 {{bit_cast involving type 'bool __attribute__((ext_vector_type(9)))' (vector of 9 'bool' values) is not allowed in a constant expression; element size 1 * element count 9 is not a multiple of the byte size 8}}
diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp
index 792dc78464b2a8..f6fbea4ab03d75 100644
--- a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp
+++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp
@@ -216,15 +216,19 @@ namespace UnnamedBitfield {
// uninitialized and it being zeroed. Those are not distinct states
// according to [temp.type]p2.
//
- // FIXME: We shouldn't track a value for unnamed bit-fields, nor number
- // them when computing field indexes.
+ // At namespace scope, multiple `using` declarations are valid (to avoid
+ // conflicts when #including), just in case they they "all refer to the same
+ // entity." This test makes use of that implicit constraint to ensure that
+ // the compiler does not "see" a difference between any of the `T`s below.
+ // cf. https://stackoverflow.com/a/31225016/151464
template <A> struct X {};
constexpr A a;
using T = X<a>;
using T = X<A{}>;
using T = X<(A())>;
- // Once we support bit-casts involving bit-fields, this should be valid too.
- using T = X<__builtin_bit_cast(A, 0)>; // expected-error {{constant}} expected-note {{not yet supported}}
+ using T = X<__builtin_bit_cast(A, 0)>;
+ using T = X<__builtin_bit_cast(A, A{})>;
+ using T = X<__builtin_bit_cast(A, (unsigned char[4]){})>;
}
namespace Temporary {
>From 2e0d3f81e4f5b623bb476bfac0278cfc6d1bd4bc Mon Sep 17 00:00:00 2001
From: Seth Pellegrino <seth at codecopse.net>
Date: Thu, 4 Jan 2024 13:52:25 -0800
Subject: [PATCH 3/3] refactor: BitCastBuffer with APInt, fix for bools
Make use of the pre-exising APInt type as a pre-established spelling of
BitVector to get much more granular with the BitCastBuffer; this also
permits more thorough usage of `const APInt&` references to make the
copies easier to optimize away and reduces the common case of
int->buffer->int from 4-7 copies down to 2. As a bonus, we also got a
bit more flexible with supporting platforms where `CHAR_BIT` is
something other than 8, addressing some of the review feedback from the
original diff, though there's plenty of follow-up work here and
elsewhere to make that a reality.
Additional, uncovers and fixes some errant behavior around `bool`s
introduced in the previous commit, and makes the handling of
`_BitInt(N)` types more consistent while preserving
backwards-compatability.
---
clang/lib/AST/ExprConstant.cpp | 717 +++++++++++-------
.../constexpr-builtin-bit-cast-bitint.cpp | 82 ++
.../SemaCXX/constexpr-builtin-bit-cast.cpp | 231 ++++--
3 files changed, 709 insertions(+), 321 deletions(-)
create mode 100644 clang/test/SemaCXX/constexpr-builtin-bit-cast-bitint.cpp
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 356cef552d3544..9d714ef09a1337 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -64,6 +64,7 @@
#include "llvm/Support/Casting.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Debug.h"
+#include "llvm/Support/Format.h"
#include "llvm/Support/SaveAndRestore.h"
#include "llvm/Support/SwapByteOrder.h"
#include "llvm/Support/TimeProfiler.h"
@@ -73,9 +74,10 @@
#include <cstdint>
#include <cstring>
#include <functional>
-#include <iomanip>
#include <iterator>
+#include <limits>
#include <optional>
+#include <valarray>
#define DEBUG_TYPE "exprconstant"
@@ -6914,112 +6916,259 @@ bool HandleOperatorDeleteCall(EvalInfo &Info, const CallExpr *E) {
//===----------------------------------------------------------------------===//
namespace {
+struct BitSlice : public std::slice {
+ BitSlice(size_t Offset, size_t Num) : std::slice(Offset, Num, 1){};
+
+ inline size_t end() const { return start() + size(); }
+};
+
+struct BitFieldInfo {
+ /// The offset (in bits) within the appropriate storage type.
+ const unsigned Offset : 16;
+
+ /// The size of the value held by the bit-field, in bits.
+ const unsigned Width : 15;
+
+ /// Whether the bit-field is signed.
+ const unsigned IsSigned : 1;
+
+ /// The storage size in bits which should be used when accessing this
+ /// bitfield.
+ const unsigned StorageSize;
+
+ /// The offset of the bitfield storage from the start of the struct.
+ const CharUnits StorageOffset;
+
+ static BitFieldInfo MakeInfo(const ASTContext &Ctx, const FieldDecl *FD,
+ const ASTRecordLayout &Layout) {
+ const unsigned StorageSize = Ctx.getTypeSize(FD->getType()),
+ FieldOffsetBits = Layout.getFieldOffset(FD->getFieldIndex());
+
+ unsigned Width = FD->getBitWidthValue(Ctx);
+ if (Width > StorageSize) {
+ // e.g. `unsigned uint8_t c : 12`
+ // we truncate to CHAR_BIT * sizeof(T)
+ // (the extra bits are padding)
+ Width = StorageSize;
+ }
+ unsigned Offset = FieldOffsetBits % StorageSize;
+ if (Ctx.getTargetInfo().isBigEndian()) {
+ // big endian bits count from MSB to LSB
+ // so a bit-field of width 16 and size 12 will
+ // occupy bits [0-11] on a little endian machine,
+ // but [3-15] on a big endian machine
+ Offset = StorageSize - (Offset + Width);
+ }
+ return {
+ Offset,
+ Width,
+ FD->getType()->isSignedIntegerOrEnumerationType(),
+ StorageSize,
+ Ctx.toCharUnitsFromBits((FieldOffsetBits / StorageSize) * StorageSize),
+ };
+ }
+};
+
struct BitCastBuffer {
- // FIXME: Its possible under the C++ standard for 'char' to not be 8 bits, but
- // we don't support a host or target where that is the case. Still, we should
- // use a more generic type in case we ever do.
- using byte_t = unsigned char;
- static_assert(std::numeric_limits<byte_t>::digits >= 8,
- "Need at least 8 bit unsigned char");
-
- SmallVector<byte_t, 32> Bytes;
- SmallVector<byte_t, 32> Valid;
-
- bool TargetIsLittleEndian;
-
- static SmallVector<byte_t> MaskAllSet(size_t Width) {
- SmallVector<byte_t> M;
- M.resize(Width);
- std::fill(M.begin(), M.end(), ~0);
- return M;
- }
-
- BitCastBuffer(CharUnits Width, bool TargetIsLittleEndian)
- : Bytes(Width.getQuantity()), Valid(Width.getQuantity()),
- TargetIsLittleEndian(TargetIsLittleEndian) {}
-
- [[nodiscard]] bool readObject(CharUnits Offset, CharUnits Width,
- SmallVectorImpl<byte_t> &Output,
- SmallVectorImpl<byte_t> const &Mask) const {
- assert(Mask.size() >= static_cast<unsigned>(Width.getQuantity()));
- assert(Output.size() >= static_cast<unsigned>(Width.getQuantity()));
- assert(Bytes.size() >=
- static_cast<unsigned>((Offset + Width).getQuantity()));
-
- SmallVector<byte_t, 8> RevMask;
- const SmallVectorImpl<byte_t> &M =
- (llvm::sys::IsLittleEndianHost != TargetIsLittleEndian)
- ? [&]() -> const SmallVectorImpl<byte_t> & {
- auto W = Width.getQuantity();
- RevMask.resize_for_overwrite(W);
- std::reverse_copy(Mask.begin(), Mask.begin() + W, RevMask.begin());
- return RevMask;
- }()
- : Mask;
-
- size_t Index = 0;
- for (CharUnits I = Offset, E = Offset + Width; I != E; ++I, ++Index) {
- const auto BufIdx = I.getQuantity();
- const auto mask = M[Index];
- // are there any bits in Mask[Index] that are not set in
- // Valid[BufIdx]? (NB: more bits can be set, that's just
- // fine)
- if ((Valid[BufIdx] & M[Index]) != M[Index])
- // If any bit of an integer is uninitialized, then the
- // whole integer is uninitialized.
+ // The number of bits in a `char`, needed to handle endianness (which is
+ // assumed to be exclusively big or little) for values with more bits than
+ // this number.
+ //
+ // No current platforms support varying this size.
+ static const uint64_t CharBit = 8;
+
+ const uint64_t BitWidth;
+ const bool IsNativeEndian;
+
+ APInt Data;
+ APInt Invalid; // Indeterminate bits
+
+ BitCastBuffer(uint64_t BitWidth, bool TargetIsLittleEndian, uint64_t CharBit)
+ : BitWidth(BitWidth),
+ IsNativeEndian(llvm::sys::IsLittleEndianHost == TargetIsLittleEndian),
+ Data(BitWidth, 0), Invalid(BitWidth, ~0, /* extend "sign" bit */ true) {
+ assert(Invalid.countl_one() == BitWidth);
+ assert(CharBit == BitCastBuffer::CharBit);
+ }
+
+ [[nodiscard]] bool readMasked(const uint64_t Offset, APInt &Output,
+ const APInt &Mask) const {
+ assert(Output.getBitWidth() == Mask.getBitWidth());
+ const BitSlice Which = {Offset, Output.getBitWidth()};
+
+ const auto read = [&](const APInt &Mask) {
+ if ((getBits(Invalid, Which) & Mask) != 0)
return false;
- Output[Index] = (Output[Index] & ~mask) | (Bytes[BufIdx] & mask);
+ Output = (Output & ~Mask) | (getBits(Data, Which) & Mask);
+ return true;
+ };
+
+ if (!IsNativeEndian && Output.getBitWidth() > CharBit) {
+ bool OK = read(Mask.byteSwap());
+ Output = Output.byteSwap();
+ return OK;
}
- if (llvm::sys::IsLittleEndianHost != TargetIsLittleEndian)
- std::reverse(Output.begin(), Output.end());
+ return read(Mask);
+ }
+
+ [[nodiscard]] inline bool readObject(const uint64_t Offset,
+ APInt &Output) const {
+ return readObject({Offset, Output.getBitWidth()}, Output);
+ }
+
+ [[nodiscard]] bool readObject(const BitSlice &Which, APInt &Output) const {
+ assert(Output.getBitWidth() <= BitWidth);
+ assert(Which.size() <= Output.getBitWidth());
+ assert(Which.end() <= BitWidth);
+ assert((IsNativeEndian || withinByte(Which) ||
+ APInt::getBitsSet(BitWidth, Which.start(), Which.end())
+ .byteSwap()
+ .isShiftedMask()) &&
+ "use readMasked instead");
+
+ if (getBits(Invalid, Which) != 0)
+ return false;
+
+ copyBitsFrom(Output, {0, Which.size()}, Data, Which);
+
+ if (!IsNativeEndian && Output.getBitWidth() > CharBit)
+ Output = Output.byteSwap();
+
return true;
}
- void writeObject(CharUnits Offset, SmallVectorImpl<byte_t> &Input,
- SmallVectorImpl<byte_t> &Mask) {
- assert(Mask.size() >= Input.size());
- assert(Bytes.size() >=
- static_cast<unsigned>(Offset.getQuantity()) + Input.size());
+ void writeMasked(const uint64_t Offset, const APInt &Input,
+ const APInt &Mask) {
+ assert(Input.getBitWidth() == Mask.getBitWidth());
+ const uint64_t BW = Input.getBitWidth();
+ const BitSlice Dest = {Offset, BW};
- // we could promise Input and Mask were `const`, except for this
- if (llvm::sys::IsLittleEndianHost != TargetIsLittleEndian) {
- std::reverse(Input.begin(), Input.end());
- // we might (will) have more mask bits than input bits
- std::reverse(Mask.begin(), Mask.begin() + Input.size());
+ auto write = [&](const APInt &Input, const APInt &Mask) {
+ assert((~getBits(Invalid, Dest) & Mask) == 0 && "overwriting data?");
+ const APInt Val = (Input & Mask) | (getBits(Data, Dest) & ~Mask);
+ const APInt Written = getBits(Invalid, Dest) ^ Mask;
+
+ copyBitsFrom(Data, Dest, Val, {0, BW});
+ copyBitsFrom(Invalid, Dest, Written, {0, BW});
+ };
+
+ if (!IsNativeEndian && BW > CharBit) {
+ write(Input.byteSwap(), Mask.byteSwap());
+ return;
}
+ write(Input, Mask);
+ }
+
+ void writeObject(const uint64_t Offset, const APInt &Input) {
+ writeObject({Offset, Input.getBitWidth()}, Input, {0, Input.getBitWidth()});
+ }
+
+ void writeObject(const BitSlice &Dst, const APInt &Input,
+ const BitSlice &Src) {
+ assert(Src.size() == Dst.size());
+ assert(Src.end() <= Input.getBitWidth());
+ assert(Dst.end() <= BitWidth);
+ assert(~getBits(Invalid, Dst) == 0 && "overwriting data?");
+ assert((IsNativeEndian || (withinByte(Src) && withinByte(Dst)) ||
+ [&] {
+ unsigned lo, len;
+ return Src.size() % 8 == 0 &&
+ APInt::getBitsSet(Src.size(), Src.start(), Src.end())
+ .byteSwap()
+ .isShiftedMask(lo, len) &&
+ lo == Src.start() && len == Src.size() &&
+ Dst.start() % CharBit == 0 && Dst.size() % CharBit == 0;
+ }()) &&
+ "use writeMasked instead");
+
+ auto write = [&](const APInt &Input) {
+ copyBitsFrom(Data, Dst, Input, Src);
+ clearBits(Invalid, Dst);
+ };
- size_t Index = 0;
- size_t BufIdx = Offset.getQuantity();
- for (byte_t &Byte : Input) {
- assert((Valid[BufIdx] & Mask[Index]) == 0 && "overwriting data?");
- Bytes[BufIdx] |= Byte & Mask[Index];
- Valid[BufIdx] |= Mask[Index];
- ++BufIdx;
- ++Index;
+ if (!IsNativeEndian && Input.getBitWidth() > CharBit) {
+ write(Input.byteSwap());
+ return;
}
+
+ write(Input);
+ }
+
+ // true iff the range described by Which from start (inclusive) to end
+ // (exclusive) refers to the same addressable byte, i.e.
+ // [0, 0) -> yes
+ // [0, 3) -> yes
+ // [0, 8) -> yes
+ // [16, 24) -> yes
+ // [123, 123) -> yes
+ // [7, 9) -> no
+ inline static bool withinByte(const BitSlice &Which) {
+ // NB: Which.start() may equal Which.end(), and either may be zero, so
+ // care must be taken here to avoid underflow
+ return Which.size() == 0 ||
+ Which.start() / CharBit + 1 == (Which.end() + CharBit - 1) / CharBit;
}
- size_t size() { return Bytes.size(); }
+ inline static APInt getBits(const APInt &Int, const BitSlice &Which) {
+ // more lenient than extractBits (which asserts `start < BitWidth`)
+ // this permits e.g. zero-width reads "one past the last bit"
+ assert(Which.end() <= Int.getBitWidth());
+ if (Which.size() == 0) {
+ return APInt::getZeroWidth();
+ }
+ return Int.extractBits(Which.size(), Which.start());
+ }
+
+ // copyBitsFrom acts like `LHS[Dst] = RHS[Src];`, if `APInt`s supported
+ // slicing
+ inline static void copyBitsFrom(APInt &LHS, const BitSlice &Dst,
+ const APInt &RHS, const BitSlice &Src) {
+ assert(Src.size() == Dst.size());
+
+ if (Src.start() > 0 || Src.end() < RHS.getBitWidth() ||
+ RHS.getBitWidth() != Dst.size()) {
+ APInt Val = RHS.lshr(Src.start()).trunc(Src.size()).zext(Dst.size());
+ LHS.insertBits(Val, Dst.start());
+ return;
+ }
+ LHS.insertBits(RHS, Dst.start());
+ }
+
+ inline static void clearBits(APInt &Int, const BitSlice &Which) {
+ unsigned Bit = Which.start(), Rem = Which.size() % 64;
+ if (Rem > 0) // else APInt crashes when Bit == 0
+ Int.insertBits(0ull, Bit, Rem);
+ Bit += Rem;
+ for (unsigned End = Which.end(); Bit < End; Bit += 64)
+ Int.insertBits(0ull, Bit, 64u);
+ }
+
+ static llvm::FormattedBytes formatInt(const APInt &Int) {
+ // implicit in the below we're assuming that CHAR_BIT is 8.
+ //
+ // this might get confusing on a PDP-10, where "bytes" were a software
+ // abstraction that varied in size (potentially even within the same
+ // program; see https://retrocomputing.stackexchange.com/a/15514).
+ //
+ // happily, this is a dump method, so we get to do non-backwards-compatible
+ // things like assume the programmer will know if they're in the extremely
+ // unlikely context where "byte" means something other than 8 bits.
+ const auto *Data = Int.getRawData();
+ const auto NumBytes = Int.getBitWidth() / 8;
+ assert(NumBytes <= Int.getNumWords() * sizeof(*Data));
+ const ArrayRef<uint8_t> AsBytes(reinterpret_cast<const uint8_t *>(Data),
+ NumBytes);
+ const unsigned int NumPerLine = 40, ByteGroupSize = 1;
+
+ return format_bytes(AsBytes, std::nullopt, NumPerLine, ByteGroupSize);
+ }
LLVM_DUMP_METHOD void dump() {
- auto pp = [](std::stringstream &SS, llvm::SmallVectorImpl<byte_t> &V) {
- bool first = true;
- for (byte_t v : V) {
- if (first)
- first = false;
- else
- SS << " ";
- SS << "0x" << std::hex << std::setw(2) << std::setfill('0')
- << static_cast<unsigned>(v);
- }
- };
- std::stringstream SS[2];
- pp(SS[0], Bytes);
- pp(SS[1], Valid);
- llvm::dbgs() << "BitCastBuffer{Bytes: [" << SS[0].str() << "], Valid: ["
- << SS[1].str() << "]}\n";
+ llvm::dbgs() << "BitCastBuffer{Bytes: [" << formatInt(Data)
+ << "], Invalid: [" << formatInt(Invalid) << "] (=> Valid: ["
+ << formatInt(~Invalid) << "])}\n";
}
};
@@ -7032,8 +7181,9 @@ class APValueToBufferConverter {
APValueToBufferConverter(EvalInfo &Info, CharUnits ObjectWidth,
const CastExpr *BCE)
- : Info(Info),
- Buffer(ObjectWidth, Info.Ctx.getTargetInfo().isLittleEndian()),
+ : Info(Info), Buffer(Info.Ctx.toBits(ObjectWidth),
+ Info.Ctx.getTargetInfo().isLittleEndian(),
+ Info.Ctx.getCharWidth()),
BCE(BCE) {}
bool visit(const APValue &Val, QualType Ty) {
@@ -7042,13 +7192,13 @@ class APValueToBufferConverter {
// Write out Val with type Ty into Buffer starting at Offset.
bool visit(const APValue &Val, QualType Ty, CharUnits Offset) {
- assert((size_t)Offset.getQuantity() <= Buffer.size());
+ assert((size_t)Info.Ctx.toBits(Offset) <= Buffer.BitWidth);
// As a special case, nullptr_t has an indeterminate value.
if (Ty->isNullPtrType())
return true;
- // Dig through Val to find the byte at Offset.
+ // Dig through Val to find the bits.
switch (Val.getKind()) {
case APValue::Indeterminate:
case APValue::None:
@@ -7139,54 +7289,34 @@ class APValueToBufferConverter {
if (!FieldVal.hasValue())
continue;
- uint64_t FieldOffsetBits = Layout.getFieldOffset(FieldIdx);
- CharUnits BufOffset = Offset;
- uint64_t BitOffset = FieldOffsetBits;
-
- unsigned int BitWidth = FD->getBitWidthValue(Info.Ctx);
-
- CharUnits TypeWidth = Info.Ctx.getTypeSizeInChars(FD->getType());
- uint64_t TypeWidthBits = Info.Ctx.toBits(TypeWidth);
- if (BitWidth > TypeWidthBits) {
- // e.g. `unsigned uint8_t c : 12`
- // we truncate to CHAR_BIT * sizeof(T)
- // (the extra bits are padding)
- BitWidth = TypeWidthBits;
- }
- if (FieldOffsetBits >= TypeWidthBits) {
- // e.g. `uint32_t : 33; uint32_t i : 12`
- // or `uint16_t : 16; unsigned uint16_t i : 12`
- BufOffset =
- BufOffset + CharUnits::fromQuantity(BitOffset / TypeWidthBits) *
- TypeWidth.getQuantity();
- BitOffset %= TypeWidthBits;
- }
-
- if (Info.Ctx.getTargetInfo().isBigEndian()) {
- // big endian bits count from MSB to LSB
- // so a bit-field of width 16 and size 12 will occupy bits [0-11] on a
- // little endian machine, but [3-15] on a big endian machine
- BitOffset = TypeWidthBits - (BitOffset + BitWidth);
+ const auto BF = BitFieldInfo::MakeInfo(Info.Ctx, FD, Layout);
+
+ APSInt BoolVal;
+ const APSInt &Val = [&]() -> const APSInt & {
+ const APSInt &Val = FieldVal.getInt();
+ if (FD->getType()->isBooleanType()) {
+ // Let's zero extend the `i1` to be the full `Width` bits
+ // Note: this works because we refuse to read boolean
+ // values that don't have their high bits zeroed; see comment
+ // in BufferToAPValueConverter::visit(BuiltinType, ...)
+ BoolVal = Val.extend(BF.Width);
+ return BoolVal;
+ }
+ return Val;
+ }();
+ assert(Val.getBitWidth() >= BF.Width);
+ if (!Buffer.IsNativeEndian && Val.getBitWidth() > 8) {
+ APInt AdjVal = (Val << BF.Offset);
+ APInt Mask = APInt::getBitsSet(Val.getBitWidth(), BF.Offset,
+ BF.Width + BF.Offset);
+
+ Buffer.writeMasked(Info.Ctx.toBits(Offset + BF.StorageOffset), AdjVal,
+ Mask);
+ } else {
+ const uint64_t BitOffset = Info.Ctx.toBits(Offset + BF.StorageOffset);
+ Buffer.writeObject({BitOffset + BF.Offset, BF.Width}, Val,
+ {0, BF.Width});
}
-
- assert(TypeWidth >= Info.Ctx.toCharUnitsFromBits(BitWidth));
-
- llvm::SmallBitVector MaskBits(Info.Ctx.toBits(TypeWidth));
- MaskBits.set(BitOffset, BitOffset + BitWidth);
- uintptr_t Store;
- ArrayRef<uintptr_t> Ref = MaskBits.getData(Store);
- SmallVector<uint8_t, 8> Mask(Ref.size() * sizeof(uintptr_t));
- std::memcpy(Mask.data(), Ref.data(), Mask.size());
- Mask.truncate(TypeWidth.getQuantity());
-
- SmallVector<uint8_t, 8> Bytes(TypeWidth.getQuantity());
-
- APSInt Val = FieldVal.getInt() << BitOffset;
- assert(Val.getBitWidth() >= BitOffset + BitWidth &&
- "lost data in APInt -> byte buffer conversion");
-
- llvm::StoreIntToMemory(Val, &*Bytes.begin(), TypeWidth.getQuantity());
- Buffer.writeObject(BufOffset, Bytes, Mask);
}
return true;
@@ -7252,13 +7382,13 @@ class APValueToBufferConverter {
if (VTy->isExtVectorBoolType()) {
// Special handling for OpenCL bool vectors:
- // Since these vectors are stored as packed bits, but we can't write
- // individual bits to the BitCastBuffer, we'll buffer all of the elements
- // together into an appropriately sized APInt and write them all out at
- // once. Because we don't accept vectors where NElts * EltSize isn't a
- // multiple of the char size, there will be no padding space, so we don't
- // have to worry about writing data which should have been left
- // uninitialized.
+ // Since these vectors are stored in memory as packed bits, but the
+ // constexpr interpreter stores them as individual 1-bit-wide APInts, we
+ // pack them together into a single appropriately sized APInt and write
+ // them all out at once. Because we don't accept vectors where NElts *
+ // EltSize isn't a multiple of the char size, there will be no padding
+ // space, so we don't have to worry about writing data which should have
+ // been left uninitialized.
bool BigEndian = Info.Ctx.getTargetInfo().isBigEndian();
llvm::APInt Res = llvm::APInt::getZero(NElts);
@@ -7267,13 +7397,10 @@ class APValueToBufferConverter {
assert(EltAsInt.isUnsigned() && EltAsInt.getBitWidth() == 1 &&
"bool vector element must be 1-bit unsigned integer!");
- Res.insertBits(EltAsInt, BigEndian ? (NElts - I - 1) : I);
+ Res.setBitVal(BigEndian ? (NElts - I - 1) : I, EltAsInt[0]);
}
- SmallVector<uint8_t, 8> Bytes(NElts / 8);
- auto Mask = BitCastBuffer::MaskAllSet(Bytes.size());
- llvm::StoreIntToMemory(Res, &*Bytes.begin(), NElts / 8);
- Buffer.writeObject(Offset, Bytes, Mask);
+ Buffer.writeObject(Info.Ctx.toBits(Offset), Res);
} else {
// Iterate over each of the elements and write them out to the buffer at
// the appropriate offset.
@@ -7288,17 +7415,39 @@ class APValueToBufferConverter {
}
bool visitInt(const APSInt &Val, QualType Ty, CharUnits Offset) {
- APSInt AdjustedVal = Val;
- unsigned Width = AdjustedVal.getBitWidth();
if (Ty->isBooleanType()) {
- Width = Info.Ctx.getTypeSize(Ty);
- AdjustedVal = AdjustedVal.extend(Width);
+ // Let's zero extend the `i1` to be the full 8 bits
+ // Note: this works because we refuse to read boolean
+ // values that don't have their high bits zeroed; see comment
+ // in BufferToAPValueConverter::visit(BuiltinType, ...)
+ unsigned Width = Info.Ctx.getTypeSize(Ty);
+ Buffer.writeObject(Info.Ctx.toBits(Offset), Val.zext(Width));
+ return true;
+ }
+
+ if (Ty->isBitIntType()) {
+ // This preserves the existing behavior that used to function like so:
+ // ```c++
+ // SmallVector<uint8_t, 8> Bytes(Width / 8);
+ // llvm::StoreIntToMemory(AdjustedVal, &*Bytes.begin(), Width / 8);
+ // Buffer.writeObject(Offset, Bytes);
+ // ```
+ // which, when provided a _BitInt(N), would write N/8 bytes (when it
+ // didn't crash)
+ const CharUnits Bytes =
+ CharUnits::fromQuantity(Info.Ctx.getIntWidth(Ty) / 8);
+ const unsigned Size = Info.Ctx.toBits(Bytes);
+ if (!Buffer.IsNativeEndian && Val.getBitWidth() > 8) {
+ Buffer.writeObject(Info.Ctx.toBits(Offset), Val.trunc(Size));
+ } else {
+ Buffer.writeObject({static_cast<size_t>(Info.Ctx.toBits(Offset)), Size},
+ Val, {0, Size});
+ }
+
+ return true;
}
- SmallVector<uint8_t, 8> Bytes(Width / 8);
- auto Mask = BitCastBuffer::MaskAllSet(Bytes.size());
- llvm::StoreIntToMemory(AdjustedVal, &*Bytes.begin(), Width / 8);
- Buffer.writeObject(Offset, Bytes, Mask);
+ Buffer.writeObject(Info.Ctx.toBits(Offset), Val);
return true;
}
@@ -7357,28 +7506,28 @@ class BufferToAPValueConverter {
return std::nullopt;
}
- std::nullopt_t badBits(QualType Ty, CharUnits Offset,
- SmallVectorImpl<BitCastBuffer::byte_t> &M) {
+ std::nullopt_t badBits(QualType Ty, const BitSlice &Want) {
Info.FFDiag(BCE->getExprLoc(), diag::note_constexpr_bit_cast_indet_dest, 1)
<< Ty << Info.Ctx.getLangOpts().CharIsSigned;
uint64_t BitWidth = Info.Ctx.getTypeSize(BCE->getType());
- uint64_t ByteWidth = Info.Ctx.toCharUnitsFromBits(BitWidth).getQuantity();
- assert(ByteWidth == Buffer.Valid.size_in_bytes());
+ uint64_t ByteWidth =
+ Info.Ctx.getTypeSizeInChars(BCE->getType()).getQuantity();
+ assert(ByteWidth == Buffer.BitWidth / Info.Ctx.getCharWidth());
- APInt Valid(BitWidth, 0);
- llvm::LoadIntFromMemory(Valid, Buffer.Valid.begin(), ByteWidth);
- APInt Mask(BitWidth, 0);
- llvm::LoadIntFromMemory(Mask, M.begin(), M.size_in_bytes());
-
- Mask = Mask.zext(Valid.getBitWidth());
- Mask <<= Info.Ctx.toBits(Offset);
+ uint64_t BW = Info.Ctx.getTypeSize(Ty);
+ APInt Indet = Buffer.Invalid.lshr(Want.start()).trunc(BW);
+ auto Mask = APInt::getBitsSet(BW, 0, Want.size());
+ if (!Buffer.IsNativeEndian && BW % 16 == 0) {
+ Indet = Indet.byteSwap();
+ Mask = Mask.byteSwap();
+ }
auto ByteAligned = true;
- APInt Missing = (~Valid & Mask);
+ APInt Missing = Indet & Mask;
assert(!Missing.isZero() && "bad bits called with no bad bits?");
llvm::SmallVector<std::pair<size_t, size_t>> MissingBitRanges;
- int NextBit = 0;
+ int NextBit = Want.start();
while (!Missing.isZero()) {
APInt Last(Missing);
int N = Missing.countr_zero();
@@ -7426,6 +7575,16 @@ class BufferToAPValueConverter {
return std::nullopt;
}
+ static bool canStoreIndeterminate(const Type *T, const EnumType *EnumSugar) {
+ // If this is std::byte or unsigned char, then its okay to store an
+ // indeterminate value.
+ bool IsStdByte = EnumSugar && EnumSugar->isStdByteType();
+ bool IsUChar =
+ !EnumSugar && (T->isSpecificBuiltinType(BuiltinType::UChar) ||
+ T->isSpecificBuiltinType(BuiltinType::Char_U));
+ return IsStdByte || IsUChar;
+ }
+
std::optional<APValue> visit(const BuiltinType *T, CharUnits Offset,
const EnumType *EnumSugar = nullptr) {
if (T->isNullPtrType()) {
@@ -7435,51 +7594,60 @@ class BufferToAPValueConverter {
APValue::NoLValuePath{}, /*IsNullPtr=*/true);
}
- CharUnits SizeOf = Info.Ctx.getTypeSizeInChars(T);
+ uint64_t SizeOf = Info.Ctx.getTypeSize(T);
- // Work around floating point types that contain unused padding bytes. This
+ // Some floating point types contain unused padding bytes. This
// is really just `long double` on x86, which is the only fundamental type
- // with padding bytes.
+ // with padding bytes. (other than `bool`s, which are handled specially
+ // below)
if (T->isRealFloatingType()) {
const llvm::fltSemantics &Semantics =
Info.Ctx.getFloatTypeSemantics(QualType(T, 0));
unsigned NumBits = llvm::APFloatBase::getSizeInBits(Semantics);
assert(NumBits % 8 == 0);
- CharUnits NumBytes = CharUnits::fromQuantity(NumBits / 8);
- if (NumBytes != SizeOf)
- SizeOf = NumBytes;
- }
-
- SmallVector<uint8_t, 8> Bytes,
- Mask = BitCastBuffer::MaskAllSet(SizeOf.getQuantity());
- Bytes.resize_for_overwrite(SizeOf.getQuantity());
- if (!Buffer.readObject(Offset, SizeOf, Bytes, Mask)) {
- // If this is std::byte or unsigned char, then its okay to store an
- // indeterminate value.
- bool IsStdByte = EnumSugar && EnumSugar->isStdByteType();
- bool IsUChar =
- !EnumSugar && (T->isSpecificBuiltinType(BuiltinType::UChar) ||
- T->isSpecificBuiltinType(BuiltinType::Char_U));
- if (!IsStdByte && !IsUChar) {
+ if (NumBits != SizeOf)
+ SizeOf = NumBits;
+ }
+
+ uint64_t BitAddr = Info.Ctx.toBits(Offset);
+ APSInt Val(SizeOf, true);
+ if (!Buffer.readObject(BitAddr, Val)) {
+ if (!canStoreIndeterminate(T, EnumSugar)) {
QualType DisplayType(EnumSugar ? (const Type *)EnumSugar : T, 0);
- return badBits(DisplayType, Offset, Mask);
+ return badBits(DisplayType, {BitAddr, Val.getBitWidth()});
}
return APValue::IndeterminateValue();
}
- APSInt Val(SizeOf.getQuantity() * Info.Ctx.getCharWidth(), true);
- llvm::LoadIntFromMemory(Val, &*Bytes.begin(), Bytes.size());
-
if (T->isIntegralOrEnumerationType()) {
Val.setIsSigned(T->isSignedIntegerOrEnumerationType());
- unsigned IntWidth = Info.Ctx.getIntWidth(QualType(T, 0));
- if (IntWidth != Val.getBitWidth()) {
- APSInt Truncated = Val.trunc(IntWidth);
- if (Truncated.extend(Val.getBitWidth()) != Val)
+ if (T->isBooleanType()) {
+ // booleans are special in that they have natural padding. However,
+ // rather than treating the padding bits as such, we instead choose to
+ // see them more like "tag" bits that are architecturally required to be
+ // zeroed, i.e. invoking the "no value of type `To` corresponding to the
+ // representation" undefined behavior clause, and therefore refusing to
+ // produce a constant value.
+ //
+ // We do this because on write, we'd like to zero-extend a `bool` out to
+ // 8 bits so that it's possible to `bit_cast<uint8_t>(false)` without
+ // additional ceremony. However, that means that if we permit any
+ // non-zero bit patterns to be cast _to_ a bool here, we'd permit a
+ // construct like the following:
+ // ```c++
+ // bit_cast<uint8_t>(bit_cast<bool>('\x02'))
+ // ```
+ // to produce a constant `0x0` (because we'll zero-extend the LSB).
+ //
+ // Note that this is different behavior than we'll want for _BitInt(N)
+ // types, where we have no desire for a bit cast from a `_BitInt(3)` to
+ // produce a constant value for the other bits.
+ if (Val.getActiveBits() > 1)
return unrepresentableValue(QualType(T, 0), Val);
- Val = Truncated;
+
+ Val = Val.trunc(1);
}
return APValue(Val);
@@ -7554,76 +7722,79 @@ class BufferToAPValueConverter {
if (!FD->isBitField())
continue;
+ // matches the existing behavior
+ if (FD->getType()->isBitIntType())
+ return unsupportedType(FD->getType());
+
// unnamed bit fields are purely padding
if (FD->isUnnamedBitfield())
continue;
- uint64_t FieldOffsetBits = Layout.getFieldOffset(FieldIdx);
- CharUnits BufOffset = Offset;
- uint64_t BitOffset = FieldOffsetBits;
-
- unsigned int BitWidth = FD->getBitWidthValue(Info.Ctx);
-
- CharUnits TypeWidth = Info.Ctx.getTypeSizeInChars(FD->getType());
- uint64_t TypeWidthBits = Info.Ctx.toBits(TypeWidth);
- if (BitWidth > TypeWidthBits) {
- // e.g. `unsigned uint8_t c : 12`
- // we truncate to CHAR_BIT * sizeof(T)
- // (the extra bits are padding)
- BitWidth = TypeWidthBits;
- }
- if (FieldOffsetBits >= TypeWidthBits) {
- // e.g. `uint32_t : 33; uint32_t i : 12`
- // or `uint16_t : 16; unsigned uint16_t i : 12`
- BufOffset =
- BufOffset + CharUnits::fromQuantity(BitOffset / TypeWidthBits) *
- TypeWidth.getQuantity();
- BitOffset %= TypeWidthBits;
- }
-
- if (Info.Ctx.getTargetInfo().isBigEndian()) {
- // big endian bits count from MSB to LSB
- // so a bit-field of width 16 and size 12 will occupy bits [0-11] on a
- // little endian machine, but [3-15] on a big endian machine
- BitOffset = TypeWidthBits - (BitOffset + BitWidth);
+ const auto BF = BitFieldInfo::MakeInfo(Info.Ctx, FD, Layout);
+ const bool isUnsigned =
+ FD->getType()->isUnsignedIntegerOrEnumerationType();
+ APSInt Val;
+ bool ReadOK;
+ const unsigned BitAddr =
+ Info.Ctx.toBits(Offset + BF.StorageOffset) + BF.Offset;
+ if (!Buffer.IsNativeEndian && BF.StorageSize > 8) {
+ Val = APSInt(BF.StorageSize, true);
+ const APInt Mask =
+ APInt::getBitsSet(BF.StorageSize, BF.Offset, BF.Offset + BF.Width);
+
+ ReadOK = Buffer.readMasked(Info.Ctx.toBits(Offset + BF.StorageOffset),
+ Val, Mask);
+
+ Val >>= BF.Offset;
+ Val = Val.trunc(BF.Width);
+ Val.setIsUnsigned(isUnsigned);
+ } else {
+ Val = APSInt(BF.Width, isUnsigned);
+ ReadOK = Buffer.readObject(BitAddr, Val);
}
- assert(TypeWidth >= Info.Ctx.toCharUnitsFromBits(BitWidth));
-
- llvm::SmallBitVector MaskBits(Info.Ctx.toBits(TypeWidth));
- MaskBits.set(BitOffset, BitOffset + BitWidth);
- uintptr_t Store;
- ArrayRef<uintptr_t> BitRef = MaskBits.getData(Store);
- SmallVector<uint8_t, 8> Mask(BitRef.size() * sizeof(uintptr_t));
- std::memcpy(Mask.data(), BitRef.data(), Mask.size());
- Mask.truncate(TypeWidth.getQuantity());
-
- SmallVector<uint8_t, 8> Bytes(TypeWidth.getQuantity());
- if (!Buffer.readObject(BufOffset, TypeWidth, Bytes, Mask)) {
+ if (!ReadOK) {
const Type *T = FD->getType().getCanonicalType().getTypePtr();
const EnumType *EnumSugar = dyn_cast<EnumType>(T);
- // If this is std::byte or unsigned char, then its okay to store an
- // indeterminate value.
- bool IsStdByte = EnumSugar && EnumSugar->isStdByteType();
- bool IsUChar =
- !EnumSugar && (T->isSpecificBuiltinType(BuiltinType::UChar) ||
- T->isSpecificBuiltinType(BuiltinType::Char_U));
- if (!IsStdByte && !IsUChar) {
+ if (!canStoreIndeterminate(T, EnumSugar)) {
QualType DisplayType(EnumSugar ? (const Type *)EnumSugar : T, 0);
- return badBits(DisplayType, BufOffset, Mask);
+ return badBits(DisplayType, {BitAddr, BF.Width});
}
+
ResultVal.getStructField(FieldIdx) = APValue::IndeterminateValue();
- } else {
- APSInt Val(Info.Ctx.toBits(TypeWidth), true);
- llvm::LoadIntFromMemory(Val, &*Bytes.begin(), TypeWidth.getQuantity());
+ continue;
+ }
- Val >>= BitOffset;
- Val = Val.trunc(BitWidth);
- Val.setIsSigned(FD->getType()->isSignedIntegerOrEnumerationType());
- Val = Val.extend(Info.Ctx.toBits(TypeWidth));
+ if (FD->getType()->isBooleanType()) {
+ // booleans are special in that they have natural padding. However,
+ // rather than treating the padding bits as such, we instead choose to
+ // see them more like "tag" bits that are architecturally required to be
+ // zeroed, i.e. invoking the "no value of type `To` corresponding to the
+ // representation" undefined behavior clause, and therefore refusing to
+ // produce a constant value.
+ //
+ // We do this because on write, we'd like to zero-extend a `bool` out to
+ // 8 bits so that it's possible to `bit_cast<uint8_t>(false)` without
+ // additional ceremony. However, that means that if we permit any
+ // non-zero bit patterns to be cast _to_ a bool here, we'd permit a
+ // construct like the following:
+ // ```c++
+ // bit_cast<uint8_t>(bit_cast<bool>('\x02'))
+ // ```
+ // to produce a constant `0x0` (because we'll zero-extend the LSB).
+ //
+ // Note that this is different behavior than we'll want for _BitInt(N)
+ // types, where we have no desire for a bit cast from a `_BitInt(3)` to
+ // produce a constant value for the other bits.
+ if (Val.getActiveBits() > 1)
+ return unrepresentableValue(FD->getType(), Val);
- ResultVal.getStructField(FieldIdx) = APValue(Val);
+ Val = Val.trunc(1);
+ } else {
+ Val = Val.extend(BF.StorageSize);
}
+
+ ResultVal.getStructField(FieldIdx) = APValue(Val);
}
return ResultVal;
@@ -7688,28 +7859,22 @@ class BufferToAPValueConverter {
Elts.reserve(NElts);
if (VTy->isExtVectorBoolType()) {
// Special handling for OpenCL bool vectors:
- // Since these vectors are stored as packed bits, but we can't read
- // individual bits from the BitCastBuffer, we'll buffer all of the
- // elements together into an appropriately sized APInt and write them all
- // out at once. Because we don't accept vectors where NElts * EltSize
- // isn't a multiple of the char size, there will be no padding space, so
- // we don't have to worry about reading any padding data which didn't
- // actually need to be accessed.
+ // Since these vectors are stored in memory as packed bits, but the
+ // constexpr interpreter wants to store them as individual 1-bit-wide
+ // APInts, so we unpack them here. Because we don't accept vectors where
+ // NElts * EltSize isn't a multiple of the char size, there will be no
+ // padding space, so we don't have to worry about reading any padding data
+ // which didn't actually need to be accessed.
bool BigEndian = Info.Ctx.getTargetInfo().isBigEndian();
- size_t Width = NElts / 8;
- SmallVector<uint8_t, 8> Bytes, Mask = BitCastBuffer::MaskAllSet(Width);
- Bytes.resize_for_overwrite(Width);
- if (!Buffer.readObject(Offset, CharUnits::fromQuantity(Width), Bytes,
- Mask))
- return std::nullopt;
-
- APSInt SValInt(NElts, true);
- llvm::LoadIntFromMemory(SValInt, &*Bytes.begin(), Bytes.size());
+ const unsigned BitAddr = Info.Ctx.toBits(Offset);
+ APSInt Val(NElts, true);
+ if (!Buffer.readObject(BitAddr, Val))
+ return badBits(QualType(VTy, 0), {BitAddr, Val.getBitWidth()});
for (unsigned I = 0; I < NElts; ++I) {
llvm::APInt Elt =
- SValInt.extractBits(1, (BigEndian ? NElts - I - 1 : I) * EltSize);
+ Val.extractBits(1, (BigEndian ? NElts - I - 1 : I) * EltSize);
Elts.emplace_back(
APSInt(std::move(Elt), !EltTy->isSignedIntegerType()));
}
diff --git a/clang/test/SemaCXX/constexpr-builtin-bit-cast-bitint.cpp b/clang/test/SemaCXX/constexpr-builtin-bit-cast-bitint.cpp
new file mode 100644
index 00000000000000..3acf82dc0fb4a6
--- /dev/null
+++ b/clang/test/SemaCXX/constexpr-builtin-bit-cast-bitint.cpp
@@ -0,0 +1,82 @@
+// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -triple x86_64-apple-macosx10.14.0 %s
+// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -triple x86_64-apple-macosx10.14.0 %s -fno-signed-char
+// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -triple aarch64_be-linux-gnu %s
+
+// This is separate from constexpr-builtin-bit-cast.cpp because clangd17 seems to behave
+// poorly around __BitInt(N) types, and this isolates that unfortunate behavior to one file
+//
+// hopefully a future clangd will not crash or lose track of its syntax highlighting, at which
+// point the "bit_precise" namespace ought to be merged back into *bit-cast.cpp.
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+# define LITTLE_END 1
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+# define LITTLE_END 0
+#else
+# error "huh?"
+#endif
+
+using uint8_t = unsigned char;
+
+template <class To, class From>
+constexpr To bit_cast(const From &from) {
+ static_assert(sizeof(To) == sizeof(From));
+ return __builtin_bit_cast(To, from);
+}
+
+namespace bit_precise {
+// ok so it's a little bit of a lie to say we don't support _BitInt in any casts; we do in fact
+// support casting _from_ a _BitInt(N), at least some of the time
+static_assert(bit_cast<uint8_t, _BitInt(8)>(0xff) == 0xff);
+template <int N> struct bytes { uint8_t b[N]; };
+static_assert(bit_cast<bytes<2>, _BitInt(12)>(0xff).b[(LITTLE_END ? 0 : /* fixme */ 0)] == 0xff);
+static_assert(bit_cast<bytes<4>, _BitInt(24)>(0xff).b[(LITTLE_END ? 0 : /* fixme */ 2)] == 0xff);
+
+enum byte : unsigned char {}; // not std::byte
+
+constexpr _BitInt(7) z = 0x7f;
+constexpr auto bad_cast = __builtin_bit_cast(byte, z); // expected-error {{constant expression}}
+// expected-note at -1 {{'bit_precise::byte' is invalid}}
+// expected-note at -2 {{byte [0]}}
+
+#if __clang_major__ > 17
+// This is #ifdef'd off to stop clangd from crashing every time I open this file in my editor
+// fixme? this crashes clang17 and before
+constexpr auto unsupported_cast = __builtin_bit_cast(uint8_t, z); // expected-error {{constant expression}}
+// expected-note at -1 {{subobject of type 'const uint8_t' (aka 'const unsigned char') is not initialized}}
+#endif
+
+// expected-note at +1 {{constexpr bit cast involving type '_BitInt(8)' is not yet supported}}
+constexpr auto _n = __builtin_bit_cast(_BitInt(8), (uint8_t)0xff); // expected-error {{constant expression}}
+
+// expected-note at +1 {{constexpr bit cast involving type '_BitInt(7)' is not yet supported}}
+constexpr auto _m = __builtin_bit_cast(_BitInt(7), (uint8_t)0xff); // expected-error {{constant expression}}
+
+// fixme: support _BitInt
+// struct bitints {
+// _BitInt(2) x;
+// signed _BitInt(4) y;
+// };
+//
+// constexpr auto bi = bit_cast<bitints, uint16_t>(0xff'ff);
+// static_assert(bi.x == 0x3);
+// static_assert(bi.y == -8);
+
+// fixme?: the syntax highlighting here is a little off (`signed` and `constexpr` both lose their "keyword" coloring)
+struct BF {
+ _BitInt(2) x : 2;
+ signed _BitInt(3) y : 2;
+ // expected-warning at +1 {{exceeds the width of its type}}
+ _BitInt(3) z : 4; // "oversized" bit field
+};
+
+// expected-note at +1 {{constexpr bit cast involving type '_BitInt(2)' is not yet supported}}
+constexpr auto bf = __builtin_bit_cast(BF, (uint8_t)0xff); // expected-error {{must be initialized by a constant expression}}
+
+// fixme: support _BitInt
+// constexpr auto bf = bit_cast<BF, uint8_t>(0xff);
+// static_assert(bf.x == 0x3);
+// static_assert(bf.y == -4); // or +4 ?
+// static_assert(bf.z == 0x7);
+
+} // namespace bit_precise
diff --git a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp
index 29d046e2def3db..1baff5f56941a0 100644
--- a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp
+++ b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp
@@ -10,16 +10,14 @@
# error "huh?"
#endif
-template <class T, class V> struct is_same {
- static constexpr bool value = false;
-};
-template <class T> struct is_same<T, T> {
- static constexpr bool value = true;
-};
-
static_assert(sizeof(int) == 4);
static_assert(sizeof(long long) == 8);
+using uint8_t = unsigned char;
+using uint16_t = unsigned __INT16_TYPE__;
+using uint32_t = unsigned __INT32_TYPE__;
+using uint64_t = unsigned __INT64_TYPE__;
+
template <class To, class From>
constexpr To bit_cast(const From &from) {
static_assert(sizeof(To) == sizeof(From));
@@ -134,10 +132,6 @@ void test_partially_initialized() {
static_assert(fine.x == 1 && fine.y == 5);
}
-namespace std {
-enum byte : unsigned char {};
-} // namespace std
-
template <int N, typename T = unsigned char, int Pad = 0>
struct bits {
T : Pad;
@@ -154,7 +148,6 @@ constexpr bool operator==(const struct bits<N, T, P>& lhs, const struct bits<N,
}
void test_bitfields() {
- using uint16_t = unsigned __INT16_TYPE__;
{
struct Q {
// cf. CGBitFieldInfo
@@ -167,15 +160,47 @@ void test_bitfields() {
// the LSB here.
uint16_t q : 15;
};
- constexpr unsigned char bits[2] = {0xf3, 0xef};
- constexpr Q q = bit_cast<Q>(bits);
- static_assert(bit_cast<uint16_t>(bits) == (LITTLE_END
+ constexpr unsigned char bytes[2] = {0xf3, 0xef};
+ constexpr Q q = bit_cast<Q>(bytes);
+ static_assert(q.q == (LITTLE_END ? 0x6ff3 : (0xf3ee >> 1)));
+ static_assert(bit_cast<uint16_t>(bytes) == (LITTLE_END
? 0xeff3
: 0xf3ef),
"bit-field casting ought to match \"whole\"-field casting");
- static_assert(q.q == (LITTLE_END ? 0x6ff3 : (0xf3ee >> 1)));
+
+ // similarly, "skip 1 bit of padding" followed by "read 9 bits"
+ // will truncate (shift out) either the LSB (little endian) or MSB (big endian)
+ static_assert((0xf3ee >> 1) == 0x79f7);
+ static_assert(0x01cf == (0xf3ef >> (16-9-1) & 0x1ff));
+ static_assert(bit_cast<bits<9, uint16_t, 1>>(q) == (LITTLE_END
+ ? (0xeff3 >> 1) & 0x1ff
+ : (0xf3ef >> (16-9-1)) & 0x1ff));
+
+ #if LITTLE_END == 0
+ // expected-note at +5 {{bit [0]}}
+ #else
+ // expected-note at +3 {{bit [15]}}
+ #endif
+ // expected-error at +1 {{constant expression}}
+ constexpr auto _i = __builtin_bit_cast(bits<15, uint16_t, 1>, q);
+ // expected-note at -1 {{indeterminate}}
}
+ static_assert(round_trip<bits<8>, uint8_t>(0x8c) == 0x8c);
+ static_assert(round_trip<bits<32, uint32_t>, uint32_t>(0x8c0f'fee5) == 0x8c0ffee5);
+
+ #define MSG "endianness matters even with <=8-bit fields"
+ static_assert(bit_cast<bits<8, uint16_t, 7>, uint16_t>(0xcafe) == (LITTLE_END
+ ? 0x95
+ : 0x7f), MSG);
+ static_assert(bit_cast<bits<4, uint16_t, 10>, uint16_t>(0xcafe) == (LITTLE_END
+ ? 0x2
+ : 0xf), MSG);
+ static_assert(bit_cast<bits<4, uint32_t, 19>, uint32_t>(0xa1cafe) == (LITTLE_END
+ ? 0x4
+ : 0x5), MSG);
+ #undef MSG
+
struct S {
// little endian:
// MSB .... .... LSB
@@ -209,7 +234,7 @@ void test_bitfields() {
return r == other.r;
}
};
- using T = bits<31, signed long long>;
+ using T = bits<31, signed __INT64_TYPE__>;
constexpr R r{0x4ac0ffee};
constexpr T t = bit_cast<T>(r);
@@ -220,16 +245,16 @@ void test_bitfields() {
struct U {
// expected-warning at +1 {{exceeds the width of its type}}
- unsigned __INT32_TYPE__ trunc : 33;
- unsigned __INT32_TYPE__ u : 31;
+ uint32_t trunc : 33;
+ uint32_t u : 31;
constexpr bool operator==(U const &other) const {
return trunc == other.trunc && u == other.u;
}
};
struct V {
- unsigned __INT64_TYPE__ notrunc : 32;
- unsigned __INT64_TYPE__ : 1;
- unsigned __INT64_TYPE__ v : 31;
+ uint64_t notrunc : 32;
+ uint64_t : 1;
+ uint64_t v : 31;
constexpr bool operator==(V const &other) const {
return notrunc == other.notrunc && v == other.v;
}
@@ -244,7 +269,7 @@ void test_bitfields() {
static_assert(round_trip<V>(u) == u, MSG);
static_assert(round_trip<U>(v) == v, MSG);
- constexpr auto w = bit_cast<bits<12, unsigned long, 33>>(u);
+ constexpr auto w = bit_cast<bits<12, uint64_t, 33>>(u);
static_assert(w == (LITTLE_END
? 0x4ac0ffee & 0xFFF
: (0x4ac0ffee & (0xFFF << (31 - 12))) >> (31-12)
@@ -308,14 +333,20 @@ void test_bitfields() {
}
}
+namespace std {
+enum byte : unsigned char {};
+} // namespace std
+
+using uint8_t = unsigned char;
+
template<int N>
-struct bytebuf {
- using size_t = int;
- unsigned char bytes[N];
+struct bytes {
+ using size_t = unsigned int;
+ unsigned char d[N];
constexpr unsigned char &operator[](size_t index) {
if (index < N)
- return bytes[index];
+ return d[index];
}
};
@@ -342,15 +373,14 @@ void bitfield_indeterminate() {
// expected-note at +1 {{subobject declared here}}
unsigned char mem[sizeof(BF)];
};
- // expected-error at +3 {{initialized by a constant expression}}
- // zzexpected-note at +2 {{bad bits}}
+ // expected-error at +2 {{initialized by a constant expression}}
// expected-note at +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<bytebuf<4>>(B);
+ return bit_cast<bytes<4>>(B);
};
static_assert(f()[0] + f()[1] + f()[2] == 0xc0 + 0xff + 0xee);
@@ -664,13 +694,109 @@ namespace test_bool {
// expected-note at +1 {{cannot be represented in type 'bool'}}
constexpr bool test_bad_bool = __builtin_bit_cast(bool, 'A'); // expected-error {{must be initialized by a constant expression}}
-static_assert(check_round_trip<signed char>(true));
-static_assert(check_round_trip<unsigned char>(false));
-static_assert(check_round_trip<bool>(false));
+static_assert(round_trip<signed char>(true));
+static_assert(round_trip<unsigned char>(true));
+static_assert(round_trip<bool>(false) == false);
+
+static_assert(static_cast<uint8_t>(false) == 0x0);
+static_assert(bit_cast<uint8_t>(false) == 0x0);
+static_assert(static_cast<uint8_t>(true) == 0x1);
+static_assert(bit_cast<uint8_t>(true) == 0x1);
+
+static_assert(round_trip<bool, uint8_t>(0x01) == 0x1);
+static_assert(round_trip<bool, uint8_t>(0x00) == 0x0);
+// expected-note at +2 {{cannot be represented in type 'bool'}}
+// expected-error at +1 {{constant expression}}
+constexpr auto test_bad_bool2 = __builtin_bit_cast(bool, (uint8_t)0x02);
-static_assert(check_round_trip<bool>((char)0));
-static_assert(check_round_trip<bool>((char)1));
+#if LITTLE_END == 1
+constexpr auto okbits = bit_cast<bits<1>>(true);
+#else
+constexpr auto okbits = bit_cast<bits<1, uint8_t, 7>>(true);
+#endif
+static_assert(okbits == 0x1);
+// expected-note at +3 {{bit [1-7]}}
+// expected-note at +2 {{or 'std::byte'; 'bool' is invalid}}
+// expected-error at +1 {{constant expression}}
+constexpr auto _weird_bool = __builtin_bit_cast(bool, okbits);
+
+// these don't work because we're trying to read the whole 8 bits to ensure
+// the value is representable, as above
+// static_assert(round_trip<bool, bits<1>>({0x1}) == 0x1);
+// static_assert(round_trip<bool, bits<1>>({0x0}) == 0x0);
+
+// these work because we're only reading 1 bit of "bool" to ensure
+// "representability"
+static_assert(round_trip<bits<1, bool>, bits<1>>({0x1}) == 0x1);
+static_assert(round_trip<bits<1, bool>, bits<1>>({0x0}) == 0x0);
+
+template <const int P, class B = bool>
+constexpr bool extract_bit(unsigned char v) {
+ return static_cast<bool>(bit_cast<bits<1, B, P>>(v).bits);
}
+// 0xA5 is a palindrome, so endianness doesn't matter
+// (counting LSB->MSB is the same as MSB->LSB)
+static_assert(extract_bit<0>(0xA5) == 0x1);
+static_assert(extract_bit<2>(0xA5) == 0x1);
+static_assert(extract_bit<5>(0xA5) == 0x1);
+static_assert(extract_bit<7>(0xA5) == 0x1);
+
+static_assert(extract_bit<1>(0xA5) == 0x0);
+static_assert(extract_bit<3>(0xA5) == 0x0);
+static_assert(extract_bit<4>(0xA5) == 0x0);
+static_assert(extract_bit<6>(0xA5) == 0x0);
+
+enum byte : unsigned char {}; // not std::byte or unsigned char
+
+static_assert(extract_bit<5, byte>('\xa5') == 0x1);
+
+struct pad {
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ bool : 5; // push field down to the LSB
+#endif
+ bool b : 3;
+};
+
+static_assert(bit_cast<pad, uint8_t>(0b001).b == true);
+static_assert(bit_cast<pad, uint8_t>(0b000).b == false);
+
+// expected-note at +1 {{cannot be represented in type 'bool'}}
+constexpr auto _bad_bool3 = __builtin_bit_cast(pad, (uint8_t)0b110); // expected-error {{must be initialized by a constant expression}}
+
+struct S {
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ byte : 7;
+#endif
+ byte z : 1;
+};
+
+constexpr auto s = bit_cast<S>(pad{1});
+static_assert(s.z == 0x1);
+
+// expected-note at +3 {{bit [1-2]}}
+// expected-note at +2 {{or 'std::byte'; 'bool' is invalid}}
+// expected-error at +1 {{constant expression}}
+constexpr auto _bad_bool4 = __builtin_bit_cast(pad, s);
+
+
+// `bool` includes padding bits, but *which* single bit stores the
+// value is under-specified. These tests not-so-secretly assert that
+// it's in fact the LSB that the compiler "sees" as the value.
+struct pack {
+ bool a : 1;
+ bool b : 1;
+
+ // 1 bit of value, 5 bits of padding
+ bool c : 6;
+};
+
+constexpr auto packed = bit_cast<pack, uint8_t>(LITTLE_END ? 0x07 : 0xc1);
+static_assert(packed.a && packed.b && packed.c);
+
+static_assert(bit_cast<bits<2, uint8_t, 0>>(packed) == 0x3);
+static_assert(bit_cast<bits<1, uint8_t, LITTLE_END ? 2 : 7>>(packed) == 0x1);
+
+} // namespace test_bool
namespace test_long_double {
#ifdef __x86_64
@@ -680,9 +806,7 @@ constexpr __int128_t test_cast_to_int128 = __builtin_bit_cast(__int128_t, (long
constexpr long double ld = 3.1425926539;
-struct bytes {
- unsigned char d[16];
-};
+using bytes = bytes<16>;
static_assert(check_round_trip<bytes>(ld));
@@ -696,10 +820,10 @@ constexpr bool f(bool read_uninit) {
};
for (int i = 0; i != 10; ++i)
- if (ld_bytes[i] != b.d[i])
+ if (ld_bytes[i] != b[i])
return false;
- if (read_uninit && b.d[10]) // expected-note{{read of uninitialized object is not allowed in a constant expression}}
+ if (read_uninit && b[10]) // expected-note{{read of uninitialized object is not allowed in a constant expression}}
return false;
return true;
@@ -722,7 +846,7 @@ static_assert(bit_cast<long double>(ld539) == fivehundredandthirtynine);
#else
static_assert(round_trip<__int128_t>(34.0L));
#endif
-}
+} // namespace test_long_double
namespace test_vector {
@@ -746,9 +870,9 @@ typedef bool bool32 __attribute__((ext_vector_type(32)));
typedef bool bool128 __attribute__((ext_vector_type(128)));
static_assert(bit_cast<unsigned char>(bool8{1,0,1,0,1,0,1,0}) == (LITTLE_END ? 0x55 : 0xAA));
-static_assert(check_round_trip<bool8>(static_cast<unsigned char>(0)));
-static_assert(check_round_trip<bool8>(static_cast<unsigned char>(1)));
-static_assert(check_round_trip<bool8>(static_cast<unsigned char>(0x55)));
+static_assert(round_trip<bool8>('\x00') == 0);
+static_assert(round_trip<bool8>('\x01') == 0x1);
+static_assert(round_trip<bool8>('\x55') == 0x55);
static_assert(bit_cast<unsigned short>(bool16{1,1,1,1,1,0,0,0, 1,1,1,1,0,1,0,0}) == (LITTLE_END ? 0x2F1F : 0xF8F4));
@@ -766,4 +890,21 @@ constexpr bool9 bad_short_to_bool9 = __builtin_bit_cast(bool9, static_cast<unsig
// expected-note at +1 {{bit_cast involving type 'bool __attribute__((ext_vector_type(17)))' (vector of 17 'bool' values) is not allowed in a constant expression; element size 1 * element count 17 is not a multiple of the byte size 8}}
constexpr bool17 bad_int_to_bool17 = __builtin_bit_cast(bool17, 0x0001CAFEU);
-}
+struct pad {
+ unsigned short s;
+ unsigned char c;
+};
+constexpr auto p = bit_cast<pad>(bit_cast<bool32>(0xa1c0ffee));
+static_assert(p.s == (LITTLE_END ? 0xffee : 0xa1c0));
+static_assert(p.c == (LITTLE_END ? 0xc0 : 0xff));
+
+#if LITTLE_END == 1
+// expected-note at +5 {{for byte [3]}}
+#else
+// expected-note at +3 {{for byte [0]}}
+#endif
+// expected-note at +1 {{indeterminate value}}
+constexpr auto _bad_p = __builtin_bit_cast(bool32, p); // expected-error {{initialized by a constant expression}}
+
+
+} // namespace test_vector
More information about the cfe-commits
mailing list