[clang] [clang] Implement a bitwise_copyable builtin type trait. (PR #86512)

Haojian Wu via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 16 04:48:05 PDT 2024


https://github.com/hokein updated https://github.com/llvm/llvm-project/pull/86512

>From 10d4b9e505d5ad7476071153d2d13799f5b5cf1f Mon Sep 17 00:00:00 2001
From: Haojian Wu <hokein.wu at gmail.com>
Date: Mon, 25 Mar 2024 15:10:51 +0100
Subject: [PATCH 1/2] [clang] Implement a bitwise_copyable builtin type trait.

This patch implements a `__is_bitwise_copyable` builtin in clang.

The bitwise copyable types act as the trivially copyable types, but
they support a wider range of types (e.g. classes with virtual methods) --
their underlying types can be safely copied by `memcopy` or `memmove`,
the clang compiler guarantees that both source and destination objects
have the same *object* representations after the copy operation, and the
lifetime of the destination object implicitly starts.

A particular use case of this builtin is to clone an object via memcopy
(without running the constructor):

```
Message* clone(const Message* src, char* buffer, int size) {
  if constexpr __is_bitwise_copyable(Message) {
    // bitwise copy to buffer, and implicitly create objects at the buffer
    __builtin_memcpy(buffer, src, size);
    return std::launder(reinterpret_cast<Message*>(buffer));
  }
  // Fallback the operator new, which calls the constructor to start the lifetime.
  return new(buffer) Message(src);
}
```

Note that the definition of bitwise copyable is not tied to the Rule Of
Five, so users of this builtin must guarantee that program semantic constraints
are satisfied, e.g. no double resource deallocations.

Context: https://discourse.llvm.org/t/extension-for-creating-objects-via-memcpy
---
 clang/include/clang/AST/Type.h                | 12 +++++++++
 clang/include/clang/Basic/TokenKinds.def      |  1 +
 clang/lib/AST/Type.cpp                        | 23 ++++++++++++++++
 clang/lib/Sema/SemaExprCXX.cpp                |  4 +++
 .../SemaCXX/builtin-is-bitwise-copyable.cpp   | 26 +++++++++++++++++++
 5 files changed, 66 insertions(+)
 create mode 100644 clang/test/SemaCXX/builtin-is-bitwise-copyable.cpp

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 99f45d518c7960..86d5f8320ac58a 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -918,6 +918,18 @@ class QualType {
   /// Return true if this is a trivially copyable type (C++0x [basic.types]p9)
   bool isTriviallyCopyableType(const ASTContext &Context) const;
 
+  /// Return true if this is a bitwise copyable type.
+  ///
+  /// This is an extension in clang: bitwise copyable types act as trivially
+  /// copyable types, underlying bytes of bitwise copyable type can be safely
+  /// copied by memcpy or memmove. Clang guarantees that both source and
+  /// destination objects have the same **object** representations after the
+  /// copy, and the lifetime of the destination object implicitly starts.
+  ///
+  /// bitwise copyable types cover a wider range of types, e.g. classes with
+  /// virtual methods.
+  bool isBitwiseCopyableType(const ASTContext &Context) const;
+
   /// Return true if this is a trivially copyable type
   bool isTriviallyCopyConstructibleType(const ASTContext &Context) const;
 
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index a27fbed358a60c..b1de15a7e39f48 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -527,6 +527,7 @@ TYPE_TRAIT_2(__is_pointer_interconvertible_base_of, IsPointerInterconvertibleBas
 #include "clang/Basic/TransformTypeTraits.def"
 
 // Clang-only C++ Type Traits
+TYPE_TRAIT_1(__is_bitwise_copyable, IsBitwiseCopyable, KEYCXX)
 TYPE_TRAIT_1(__is_trivially_relocatable, IsTriviallyRelocatable, KEYCXX)
 TYPE_TRAIT_1(__is_trivially_equality_comparable, IsTriviallyEqualityComparable, KEYCXX)
 TYPE_TRAIT_1(__is_bounded_array, IsBoundedArray, KEYCXX)
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index cb22c91a12aa89..9d13256de3327c 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2718,6 +2718,29 @@ bool QualType::isTriviallyCopyableType(const ASTContext &Context) const {
                                      /*IsCopyConstructible=*/false);
 }
 
+bool QualType::isBitwiseCopyableType(const ASTContext & Context) const {
+  QualType CanonicalType = getCanonicalType();
+  if (CanonicalType->isIncompleteType() || CanonicalType->isDependentType())
+    return false;
+  // Trivially copyable types are bitwise copyable, e.g. scalar types.
+  if (CanonicalType.isTriviallyCopyableType(Context))
+    return true;
+
+  if (CanonicalType->isArrayType())
+    return Context.getBaseElementType(CanonicalType)
+        .isBitwiseCopyableType(Context);
+
+  if (const auto *RD = CanonicalType->getAsCXXRecordDecl()) {
+    for (auto *const Field : RD->fields()) {
+      QualType T = Context.getBaseElementType(Field->getType());
+      if (!T.isBitwiseCopyableType(Context))
+        return false;
+    }
+    return true;
+  }
+  return false;
+}
+
 bool QualType::isTriviallyCopyConstructibleType(
     const ASTContext &Context) const {
   return isTriviallyCopyableTypeImpl(*this, Context,
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index fa7b92a43346b1..c2026a9e4ad17c 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5109,6 +5109,8 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT,
   case UTT_IsStandardLayout:
   case UTT_IsPOD:
   case UTT_IsLiteral:
+  // Clang extension:
+  case UTT_IsBitwiseCopyable:
   // By analogy, is_trivially_relocatable and is_trivially_equality_comparable
   // impose the same constraints.
   case UTT_IsTriviallyRelocatable:
@@ -5594,6 +5596,8 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
     return C.hasUniqueObjectRepresentations(T);
   case UTT_IsTriviallyRelocatable:
     return T.isTriviallyRelocatableType(C);
+  case UTT_IsBitwiseCopyable:
+    return T.isBitwiseCopyableType(C);
   case UTT_IsReferenceable:
     return T.isReferenceable();
   case UTT_CanPassInRegs:
diff --git a/clang/test/SemaCXX/builtin-is-bitwise-copyable.cpp b/clang/test/SemaCXX/builtin-is-bitwise-copyable.cpp
new file mode 100644
index 00000000000000..68e5dd21aa47d8
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-is-bitwise-copyable.cpp
@@ -0,0 +1,26 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+
+// Scalar types are bitwise copyable.
+static_assert(__is_bitwise_copyable(int));
+static_assert(__is_bitwise_copyable(int*));
+// array
+static_assert(__is_bitwise_copyable(int[10]));
+
+
+struct Forward; // expected-note 2{{forward declaration of 'Forward'}}
+static_assert(!__is_bitwise_copyable(Forward)); // expected-error {{incomplete type 'Forward' used in type trait expression}}
+
+struct Foo { int a; };
+static_assert(__is_bitwise_copyable(Foo));
+
+struct DynamicClass { virtual int Foo(); };
+static_assert(__is_bitwise_copyable(DynamicClass));
+
+template <typename T>
+void TemplateFunction() {
+  static_assert(__is_bitwise_copyable(T)); // expected-error {{incomplete type 'Forward' used in type trait expression}}
+}
+void CallTemplateFunc() {
+  TemplateFunction<Forward>(); // expected-note {{in instantiation of function template specialization}}
+  TemplateFunction<Foo>();
+}

>From f64fe4379fe1ec18b048c8626af932646fb4dc22 Mon Sep 17 00:00:00 2001
From: Haojian Wu <hokein.wu at gmail.com>
Date: Tue, 16 Apr 2024 13:25:19 +0200
Subject: [PATCH 2/2] Address review comments.

---
 clang/docs/LanguageExtensions.rst             | 44 +++++++++++++++++++
 clang/include/clang/AST/Type.h                | 16 +++----
 clang/include/clang/Basic/TokenKinds.def      |  3 +-
 clang/lib/AST/Type.cpp                        | 15 +++++--
 clang/lib/Sema/SemaExprCXX.cpp                |  7 ++-
 .../SemaCXX/builtin-is-bitwise-cloneable.cpp  | 41 +++++++++++++++++
 .../SemaCXX/builtin-is-bitwise-copyable.cpp   | 26 -----------
 7 files changed, 107 insertions(+), 45 deletions(-)
 create mode 100644 clang/test/SemaCXX/builtin-is-bitwise-cloneable.cpp
 delete mode 100644 clang/test/SemaCXX/builtin-is-bitwise-copyable.cpp

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 05c8f765b55695..2306066310a8d6 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -3958,6 +3958,50 @@ Note that the `size` argument must be a compile time constant.
 
 Note that this intrinsic cannot yet be called in a ``constexpr`` context.
 
+``__is_bitwise_cloneable``
+-------------------------
+
+A type trait is used to check whether a type can be safely copied by memcpy.
+
+**Syntax**:
+
+.. code-block:: c++
+
+  bool __is_bitwise_cloneable(Type)
+
+** Example of Use**:
+
+.. code-block:: c++
+
+  // Return a cloned object of the given default instance.
+  Foo* Clone(const Foo* default_instance, char* buffer, unsigned size) {
+    if constexpr __is_bitwise_cloneable(decltype(*default_instance)) {
+      // Fast path via memcopy, without calling class constructor.
+      memcpy(buffer, default_instance, size);
+      // Explicitly start the lifetime of the cloned object.
+      return __builtin_start_object_lifetime(reinterpret_cast<Foo*>(buffer));
+    }
+    // Fallback the operator new, which invoke the class constructor.
+    return new(buffer) Foo(*default_instance);
+  }
+
+**Description**:
+
+It is common for library owners to perform memcpy/memmove on types that aren't
+trivally copyable for performance reason. However, according to the C++ standard,
+it is undefined bheavior to mempcy non-trivially-copyable types, even though
+it may work in pratice. This builtin is designed to bridge that gap.
+
+Objects of bitwise cloneable types can be bitwise copied by memcpy/memmove. The
+Clang compiler warrants that this behavior is well defined, and won't be
+broken by compiler optimizations.
+
+After the copy, the lifetime of the new object isn't started yet (unless the
+type is trivially copyable). Users must explicitly start its lifetime by the
+`__builtin_start_object_lifetime` mechanism to avoid undefined behavior.
+
+This builtin can be used in constant expressions.
+
 Atomic Min/Max builtins with memory ordering
 --------------------------------------------
 
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 86d5f8320ac58a..c5826643a5d991 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -918,17 +918,13 @@ class QualType {
   /// Return true if this is a trivially copyable type (C++0x [basic.types]p9)
   bool isTriviallyCopyableType(const ASTContext &Context) const;
 
-  /// Return true if this is a bitwise copyable type.
+  /// Return true if the type is safe to bitwise copy by memcpy.
   ///
-  /// This is an extension in clang: bitwise copyable types act as trivially
-  /// copyable types, underlying bytes of bitwise copyable type can be safely
-  /// copied by memcpy or memmove. Clang guarantees that both source and
-  /// destination objects have the same **object** representations after the
-  /// copy, and the lifetime of the destination object implicitly starts.
-  ///
-  /// bitwise copyable types cover a wider range of types, e.g. classes with
-  /// virtual methods.
-  bool isBitwiseCopyableType(const ASTContext &Context) const;
+  /// This is an extension in clang: bitwise clonable types act as trivially
+  /// copyable types, their underlying bytes can be safely copied by memcpy or
+  /// memmove. Clang guarantees that the destination has the same **object**
+  /// representations after the copy.
+  bool isBitwiseCloneableType(const ASTContext &Context) const;
 
   /// Return true if this is a trivially copyable type
   bool isTriviallyCopyConstructibleType(const ASTContext &Context) const;
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index b1de15a7e39f48..8ac45d5fa8138e 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -527,7 +527,6 @@ TYPE_TRAIT_2(__is_pointer_interconvertible_base_of, IsPointerInterconvertibleBas
 #include "clang/Basic/TransformTypeTraits.def"
 
 // Clang-only C++ Type Traits
-TYPE_TRAIT_1(__is_bitwise_copyable, IsBitwiseCopyable, KEYCXX)
 TYPE_TRAIT_1(__is_trivially_relocatable, IsTriviallyRelocatable, KEYCXX)
 TYPE_TRAIT_1(__is_trivially_equality_comparable, IsTriviallyEqualityComparable, KEYCXX)
 TYPE_TRAIT_1(__is_bounded_array, IsBoundedArray, KEYCXX)
@@ -539,6 +538,8 @@ TYPE_TRAIT_1(__can_pass_in_regs, CanPassInRegs, KEYCXX)
 TYPE_TRAIT_2(__reference_binds_to_temporary, ReferenceBindsToTemporary, KEYCXX)
 TYPE_TRAIT_2(__reference_constructs_from_temporary, ReferenceConstructsFromTemporary, KEYCXX)
 
+TYPE_TRAIT_1(__is_bitwise_cloneable, IsBitwiseCloneable, KEYALL)
+
 // Embarcadero Expression Traits
 EXPRESSION_TRAIT(__is_lvalue_expr, IsLValueExpr, KEYCXX)
 EXPRESSION_TRAIT(__is_rvalue_expr, IsRValueExpr, KEYCXX)
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 9d13256de3327c..f0b428e88606a7 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2718,22 +2718,29 @@ bool QualType::isTriviallyCopyableType(const ASTContext &Context) const {
                                      /*IsCopyConstructible=*/false);
 }
 
-bool QualType::isBitwiseCopyableType(const ASTContext & Context) const {
+bool QualType::isBitwiseCloneableType(const ASTContext & Context) const {
   QualType CanonicalType = getCanonicalType();
   if (CanonicalType->isIncompleteType() || CanonicalType->isDependentType())
     return false;
-  // Trivially copyable types are bitwise copyable, e.g. scalar types.
+  // Trivially copyable types are bitwise clonable, e.g. scalar types.
   if (CanonicalType.isTriviallyCopyableType(Context))
     return true;
 
   if (CanonicalType->isArrayType())
     return Context.getBaseElementType(CanonicalType)
-        .isBitwiseCopyableType(Context);
+        .isBitwiseCloneableType(Context);
 
   if (const auto *RD = CanonicalType->getAsCXXRecordDecl()) {
+    for (auto Base : RD->bases())
+      if (!Base.getType().isBitwiseCloneableType(Context))
+        return false;
+    for (auto VBase : RD->vbases())
+      if (!VBase.getType().isBitwiseCloneableType(Context))
+        return false;
+
     for (auto *const Field : RD->fields()) {
       QualType T = Context.getBaseElementType(Field->getType());
-      if (!T.isBitwiseCopyableType(Context))
+      if (!T.isBitwiseCloneableType(Context))
         return false;
     }
     return true;
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index c2026a9e4ad17c..50f4cccd649f51 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5109,8 +5109,7 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT,
   case UTT_IsStandardLayout:
   case UTT_IsPOD:
   case UTT_IsLiteral:
-  // Clang extension:
-  case UTT_IsBitwiseCopyable:
+  case UTT_IsBitwiseCloneable:
   // By analogy, is_trivially_relocatable and is_trivially_equality_comparable
   // impose the same constraints.
   case UTT_IsTriviallyRelocatable:
@@ -5596,8 +5595,8 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
     return C.hasUniqueObjectRepresentations(T);
   case UTT_IsTriviallyRelocatable:
     return T.isTriviallyRelocatableType(C);
-  case UTT_IsBitwiseCopyable:
-    return T.isBitwiseCopyableType(C);
+  case UTT_IsBitwiseCloneable:
+    return T.isBitwiseCloneableType(C);
   case UTT_IsReferenceable:
     return T.isReferenceable();
   case UTT_CanPassInRegs:
diff --git a/clang/test/SemaCXX/builtin-is-bitwise-cloneable.cpp b/clang/test/SemaCXX/builtin-is-bitwise-cloneable.cpp
new file mode 100644
index 00000000000000..b2868982e9b3e1
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-is-bitwise-cloneable.cpp
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+
+// Scalar types are bitwise clonable.
+static_assert(__is_bitwise_cloneable(int));
+static_assert(__is_bitwise_cloneable(int*));
+// array
+static_assert(__is_bitwise_cloneable(int[10]));
+
+// non-scalar types.
+static_assert(!__is_bitwise_cloneable(int&));
+
+
+struct Forward; // expected-note 2{{forward declaration of 'Forward'}}
+static_assert(!__is_bitwise_cloneable(Forward)); // expected-error {{incomplete type 'Forward' used in type trait expression}}
+
+struct Foo { int a; };
+static_assert(__is_bitwise_cloneable(Foo));
+
+struct DynamicClass { virtual int Foo(); };
+static_assert(__is_bitwise_cloneable(DynamicClass));
+
+struct Bar { int& b; }; // trivially copyable
+static_assert(__is_trivially_copyable(Bar));
+static_assert(__is_bitwise_cloneable(Bar));
+
+struct Bar2 { Bar2(const Bar2&); int& b; }; // non-trivially copyable
+static_assert(!__is_trivially_copyable(Bar2));
+static_assert(!__is_bitwise_cloneable(Bar2)); // int& non-scalar member.
+
+struct DerivedBar2 : public Bar2 {};
+static_assert(!__is_bitwise_cloneable(DerivedBar2)); // base Bar2 is non-bitwise-cloneable.
+
+
+template <typename T>
+void TemplateFunction() {
+  static_assert(__is_bitwise_cloneable(T)); // expected-error {{incomplete type 'Forward' used in type trait expression}}
+}
+void CallTemplateFunc() {
+  TemplateFunction<Forward>(); // expected-note {{in instantiation of function template specialization}}
+  TemplateFunction<Foo>();
+}
diff --git a/clang/test/SemaCXX/builtin-is-bitwise-copyable.cpp b/clang/test/SemaCXX/builtin-is-bitwise-copyable.cpp
deleted file mode 100644
index 68e5dd21aa47d8..00000000000000
--- a/clang/test/SemaCXX/builtin-is-bitwise-copyable.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
-
-// Scalar types are bitwise copyable.
-static_assert(__is_bitwise_copyable(int));
-static_assert(__is_bitwise_copyable(int*));
-// array
-static_assert(__is_bitwise_copyable(int[10]));
-
-
-struct Forward; // expected-note 2{{forward declaration of 'Forward'}}
-static_assert(!__is_bitwise_copyable(Forward)); // expected-error {{incomplete type 'Forward' used in type trait expression}}
-
-struct Foo { int a; };
-static_assert(__is_bitwise_copyable(Foo));
-
-struct DynamicClass { virtual int Foo(); };
-static_assert(__is_bitwise_copyable(DynamicClass));
-
-template <typename T>
-void TemplateFunction() {
-  static_assert(__is_bitwise_copyable(T)); // expected-error {{incomplete type 'Forward' used in type trait expression}}
-}
-void CallTemplateFunc() {
-  TemplateFunction<Forward>(); // expected-note {{in instantiation of function template specialization}}
-  TemplateFunction<Foo>();
-}



More information about the cfe-commits mailing list