https://github.com/hokein updated https://github.com/llvm/llvm-project/pull/86512
>From 10d06e728d836f4aaad7dbf1a6b06b57e4092bb1 Mon Sep 17 00:00:00 2001 From: Haojian Wu <hokein...@gmail.com> Date: Mon, 25 Mar 2024 15:10:51 +0100 Subject: [PATCH 1/3] [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 e6643469e0b33..ff60f1c4b3520 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -1120,6 +1120,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 56c4b17f769d7..ce2298543fac6 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 e31741cd44240..5c0b89545a92b 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -2731,6 +2731,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 34e12078a8c92..548e824c41b5a 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -5128,6 +5128,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: @@ -5613,6 +5615,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 0000000000000..68e5dd21aa47d --- /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 3f74c49086702ea51069d030965ca6b0a6228914 Mon Sep 17 00:00:00 2001 From: Haojian Wu <hokein...@gmail.com> Date: Tue, 16 Apr 2024 13:25:19 +0200 Subject: [PATCH 2/3] 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 a09c409f8f91a..c19dd76431d50 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -4016,6 +4016,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 ff60f1c4b3520..ae2b28958b37a 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -1120,17 +1120,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 ce2298543fac6..74591a3712fa3 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) @@ -540,6 +539,8 @@ TYPE_TRAIT_2(__reference_binds_to_temporary, ReferenceBindsToTemporary, KEYCXX) TYPE_TRAIT_2(__reference_constructs_from_temporary, ReferenceConstructsFromTemporary, KEYCXX) TYPE_TRAIT_2(__reference_converts_from_temporary, ReferenceConvertsFromTemporary, 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 5c0b89545a92b..2114ab4b54bd7 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -2731,22 +2731,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 548e824c41b5a..eeafa62fde8c6 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -5128,8 +5128,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: @@ -5615,8 +5614,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 0000000000000..b2868982e9b3e --- /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 68e5dd21aa47d..0000000000000 --- 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>(); -} >From d7f9e01ba969f965a57ee6d7fdd8b875320d765a Mon Sep 17 00:00:00 2001 From: Haojian Wu <hokein...@gmail.com> Date: Thu, 16 May 2024 10:15:44 +0200 Subject: [PATCH 3/3] add review comments --- clang/docs/LanguageExtensions.rst | 12 ++++++------ clang/include/clang/AST/Type.h | 2 ++ clang/lib/AST/Type.cpp | 3 +-- clang/test/SemaCXX/builtin-is-bitwise-cloneable.cpp | 2 ++ 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index c19dd76431d50..1ef1dbc82d7b3 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -4045,18 +4045,18 @@ A type trait is used to check whether a type can be safely copied by memcpy. **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. +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 explicitly start its lifetime by the -`__builtin_start_object_lifetime` mechanism to avoid undefined behavior. +type is trivially copyable). Users must ensure its lifetime is started to avoid +undefined behavior. This builtin can be used in constant expressions. diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index ae2b28958b37a..ab0b5dd7f5302 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -1126,6 +1126,8 @@ class QualType { /// 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. + /// + // FIXME: each call triggers a full computation, cache the result. bool isBitwiseCloneableType(const ASTContext &Context) const; /// Return true if this is a trivially copyable type diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 2114ab4b54bd7..208c7d651d48d 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -2752,8 +2752,7 @@ bool QualType::isBitwiseCloneableType(const ASTContext & Context) const { return false; for (auto *const Field : RD->fields()) { - QualType T = Context.getBaseElementType(Field->getType()); - if (!T.isBitwiseCloneableType(Context)) + if (!Field->getType().isBitwiseCloneableType(Context)) return false; } return true; diff --git a/clang/test/SemaCXX/builtin-is-bitwise-cloneable.cpp b/clang/test/SemaCXX/builtin-is-bitwise-cloneable.cpp index b2868982e9b3e..a170a145f3179 100644 --- a/clang/test/SemaCXX/builtin-is-bitwise-cloneable.cpp +++ b/clang/test/SemaCXX/builtin-is-bitwise-cloneable.cpp @@ -17,8 +17,10 @@ struct Foo { int a; }; static_assert(__is_bitwise_cloneable(Foo)); struct DynamicClass { virtual int Foo(); }; +static_assert(!__is_trivially_copyable(DynamicClass)); static_assert(__is_bitwise_cloneable(DynamicClass)); +// Trivially copyable types are always bitwise cloneable. struct Bar { int& b; }; // trivially copyable static_assert(__is_trivially_copyable(Bar)); static_assert(__is_bitwise_cloneable(Bar)); _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits