[clang] [Clang][C++20] Implement constexpr std::bit_cast for bit-fields (& [Sema] Print more static_assert exprs) (PR #74775)

via cfe-commits cfe-commits at lists.llvm.org
Thu Dec 7 14:41:57 PST 2023


https://github.com/sethp created https://github.com/llvm/llvm-project/pull/74775

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
```

A few notes for the reviewers:
- It's "stacked" on top of a change making `static_assert` print more complex expressions and binary operator call operands because that makes it a whole lot easier to debug, and it seems like there's a lot of overlap between folks working in both areas. I tried to open an actual "stacked" pair of PRs per the latest on #56636 , but I don't have the prerequisite permission to push to `users/sethp/...` and I'm not quite sure how to acquire it; if this doesn't work for you, just let me know what you'd prefer.
- Overall, I feel pretty good about how the point-of-use side worked out (i.e. in `constexpr-builtin-bit-cast.cpp`); I'm especially happy that I worked out how to get the compiler to indicate which bits were indeterminate, because that really helped me learn and understand the way the structures were being laid out in memory (especially when paired with the "stacked" change).
- The implementation side is definitely a bit messier. I'm intending to continue "factoring around" the bit-field allocation logic (as mentioned in D62825), but I'm definitely not an expert in either LLVM or C++, so suggestions would be very welcome. I also have an inkling that `APInt` is a better spelling of "fixed-width bit-buffer" than `SmallVector`, especially for this situation where we're (so far) exclusively mapping to/from `APInt`s.
- As mentioned, I'm also relatively new to C++ (well, modern C++, used in anger; I don't think cutting my teeth on the language back when C++98 was the shiny new thing really helps). That said, I've got a fairly robust background in programming and computer architecture going back some 15 years. I mention that mostly because this seems like an area with more than a few subtleties, and I'm eager for your feedback on them, even if I'm not yet fluent in the C++ dialect. I did go through the [LLVM Coding Standards](https://llvm.org/docs/CodingStandards.html) and did my best to observe its recommendations; while that document was very helpful, I know it's not likely to be complete!

To better inform the shape of work that I see as outstanding here, I was hoping to solicit your feedback on three specific points:
1. Sign extension; it seems the way the constant evaluator represents bit-fields is to use a full-width `APSInt`. That's convenient for a couple reasons (e.g. there's no need for "implicit" up/down-cast nodes in the expression tree), but it does mean that we've got to invent values for some bits that really aren't "part" of the bit-field. The least surprising way I found to do this was via sign-extension, but  that does lose track of information: for example, given `struct { signed : 1 ; signed : 31  } S`, `std::bit_cast<signed>(S)` doesn't work in a `constexpr` context, but `std::bit_cast<signed>(Ex.signed)` does. I'm not really sure what there is to be done about this one besides changing the evaluator to be bit-width-precise, which seems like a larger unit of work. It's also not really new to this change, but it's something that I noticed that seemed worth raising.
2. Error message(s); I like that the "bad bits" note indicates what ranges the evaluator was looking for and didn't find, but I'm hoping you can tell me how clearly it seems to present that. There's also the classic "first-failure recursive abort" problem that can make compilers feel so nit-picky; right now, "bad bits" only notifies about the first field the evaluator sees that it can't satisfy with a value, even if there are more such objects in the cast expression. It's also a bummer that it doesn't work for the "valid uninitialized holders" (`unsigned char`, `std::byte`). I think I can fix the former by computing the entire "mask" in one pass and then applying it in the next, so we can do a total validity check once (instead of once per field). I'm a little stumped about the latter, though, since it seems like it'd mostly just be false positives: is it possible to add some sort of "deferred" note that doesn't get emitted unless they try to use the uninitialized bytes somehow?
3. Future (type) completeness; Having the benefit of "beginners mind" here, I spent a bunch of time convincing myself that the evaluator was complete in terms of the types it currently handles. I believe it is, but if a future standard invents a new type of bit-field or a new, non-BuiltinType category of TriviallyCopyable bits, it looks to me like it'd be relatively easy to omit support for those in the bit_cast implementation. I wondered "How could we bring 'bit_cast' to the attention of an implementer working elsewhere in clang?", and took a wild swing at this in the test file. But tests are only one way to communicate this kind of thing, and I suppose the constant evaluator has plenty of other possible mismatch points between it and codegen. It seems to me that attempting to match 100% of the details by construction isn't possible, so I guess I'm curious what other strategies y'all have employed here?

Thanks for all the hard work y'all do! 

>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/2] [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 c6218a491aece..e3d46c3140741 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 04db022fe7302..53c4dda133301 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 90115284d2bd0..07501c6a08184 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 95d6a55aee66a..8f31e8947a768 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 41a7b025d0eb7..1d78915aa13e1 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/2] [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 c81d17ed64108..7020f70f7c1b0 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 986302e1fd225..356cef552d354 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 c5b8032f40b13..29d046e2def3d 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 792dc78464b2a..f6fbea4ab03d7 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 {



More information about the cfe-commits mailing list