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

Haojian Wu via cfe-commits cfe-commits at lists.llvm.org
Tue May 28 06:06:45 PDT 2024


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

>From 49747cde60dc8a1f4ed4ddcee020f71c88f35287 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] [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
    __builtin_memcpy(buffer, src, size);
    // using __builtin_launder to prevent miscompile for -fstrict-vtable-pointers.
    return __builtin_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/docs/LanguageExtensions.rst             | 28 +++++++++++++++++++
 clang/include/clang/AST/Type.h                |  8 ++++++
 clang/include/clang/Basic/TokenKinds.def      |  2 ++
 clang/lib/AST/Type.cpp                        | 11 ++++++++
 clang/lib/Sema/SemaExprCXX.cpp                |  3 ++
 ...builtin-is-bitwise-cloneable-fsanitize.cpp | 20 +++++++++++++
 .../SemaCXX/builtin-is-bitwise-cloneable.cpp  |  6 ++++
 7 files changed, 78 insertions(+)
 create mode 100644 clang/test/SemaCXX/builtin-is-bitwise-cloneable-fsanitize.cpp
 create mode 100644 clang/test/SemaCXX/builtin-is-bitwise-cloneable.cpp

diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index 46f99d0bbdd06..0168411d04f50 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -4016,6 +4016,34 @@ 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)
+
+**Description**:
+
+This trait is similar to `std::is_trivially_copyable`, but additionally allows
+to have user-defined constructors, virtual functions and virtual bases. It is up
+to the user code to guarantee that a bitwise copy results in non-broken object
+and that the lifetime of an object is properly started.
+
+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 ensure its lifetime is started 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 263b632df23ce..d79b942c599cf 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -1120,6 +1120,14 @@ 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 the type is safe to bitwise copy by memcpy.
+  ///
+  /// 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 b5a0e9df9f7ae..9c4b17465e18a 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -542,6 +542,8 @@ TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary
 // is not exposed to users.
 TYPE_TRAIT_2(/*EmptySpellingName*/, IsDeducible, 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 04f105c128872..c302a2ff2846c 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -2749,6 +2749,17 @@ bool QualType::isTriviallyCopyableType(const ASTContext &Context) const {
                                      /*IsCopyConstructible=*/false);
 }
 
+bool QualType::isBitwiseCloneableType(const ASTContext & Context) const {
+  if (const auto *RD = getCanonicalType()->getAsCXXRecordDecl()) {
+    // Never allow memcpy when we're adding poisoned padding bits to the struct.
+    // Accessing these posioned bits will trigger false alarm on
+    // SanitizeAddressFieldPadding etc.
+    if (RD->mayInsertExtraPadding())
+      return false;
+  }
+  return true;
+}
+
 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 d3e9dcb4f4399..1206e460c2428 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5125,6 +5125,7 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT,
   case UTT_IsStandardLayout:
   case UTT_IsPOD:
   case UTT_IsLiteral:
+  case UTT_IsBitwiseCloneable:
   // By analogy, is_trivially_relocatable and is_trivially_equality_comparable
   // impose the same constraints.
   case UTT_IsTriviallyRelocatable:
@@ -5618,6 +5619,8 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
     return C.hasUniqueObjectRepresentations(T);
   case UTT_IsTriviallyRelocatable:
     return T.isTriviallyRelocatableType(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-fsanitize.cpp b/clang/test/SemaCXX/builtin-is-bitwise-cloneable-fsanitize.cpp
new file mode 100644
index 0000000000000..0382e4c546f62
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-is-bitwise-cloneable-fsanitize.cpp
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux -DSANITIZER_ENABLED -fsanitize=address -fsanitize-address-field-padding=1 %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux %s
+
+struct S {
+  ~S() {}
+  virtual void foo() {}
+
+  int buffer[1];
+  int other_field = 0;
+};
+
+static_assert(!__is_trivially_copyable(S));
+#ifdef SANITIZER_ENABLED
+static_assert(sizeof(S) > 16);
+// Don't allow memcpy when the struct has poisoned padding bits.
+static_assert(!__is_bitwise_cloneable(S));
+#else
+static_assert(sizeof(S) == 16);
+static_assert(__is_bitwise_cloneable(S));
+#endif
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 0000000000000..7b8489507c5c2
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-is-bitwise-cloneable.cpp
@@ -0,0 +1,6 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+// expected-no-diagnostics
+//
+struct DynamicClass { virtual int Foo(); };
+static_assert(!__is_trivially_copyable(DynamicClass));
+static_assert(__is_bitwise_cloneable(DynamicClass));



More information about the cfe-commits mailing list