Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4016,6 +4016,30 @@ 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**:

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 and sanitizers.

For implicit-lifetime types, the lifetime of the new object is implicitly
started after the copy. For other types (e.g., classes with virtual methods),
the lifetime isn't started, and using the object results in undefined behavior
according to the C++ Standard.

This builtin can be used in constant expressions.

Atomic Min/Max builtins with memory ordering
--------------------------------------------

Expand Down
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,9 @@ Non-comprehensive list of changes in this release
``-Winvalid-constexpr`` is not enabled for the function definition, which
should result in mild compile-time performance improvements.

- Added ``__is_bitwise_cloneable`` which is used to check whether a type
can be safely copied by memcpy/memmove.

New Compiler Flags
------------------
- ``-fsanitize=implicit-bitfield-conversion`` checks implicit truncation and
Expand Down
14 changes: 14 additions & 0 deletions clang/include/clang/AST/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,20 @@ 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 using memcpy/memmove.
///
/// This is an extension in clang: bitwise cloneable types act as trivially
/// copyable types, meaning their underlying bytes can be safely copied by
/// memcpy or memmove. After the copy, the destination object has the same
/// object representation.
///
/// However, there are cases where it is not safe to copy:
/// - When sanitizers, such as AddressSanitizer, add padding with poison,
/// which can cause issues if those poisoned padding bits are accessed.
/// - Types with Objective-C lifetimes, where specific runtime
/// semantics may not be preserved during a bitwise copy.
bool isBitwiseCloneableType(const ASTContext &Context) const;

/// Return true if this is a trivially copyable type
bool isTriviallyCopyConstructibleType(const ASTContext &Context) const;

Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/TokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
37 changes: 37 additions & 0 deletions clang/lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2749,6 +2749,43 @@ bool QualType::isTriviallyCopyableType(const ASTContext &Context) const {
/*IsCopyConstructible=*/false);
}

// FIXME: each call will trigger a full computation, cache the result.
bool QualType::isBitwiseCloneableType(const ASTContext &Context) const {
auto CanonicalType = getCanonicalType();
if (CanonicalType.hasNonTrivialObjCLifetime())
return false;
if (CanonicalType->isArrayType())
return Context.getBaseElementType(CanonicalType)
.isBitwiseCloneableType(Context);

if (CanonicalType->isIncompleteType())
return false;
const auto *RD = CanonicalType->getAsRecordDecl(); // struct/union/class
if (!RD)
return true;

// Never allow memcpy when we're adding poisoned padding bits to the struct.
// Accessing these posioned bits will trigger false alarms on
// SanitizeAddressFieldPadding etc.
if (RD->mayInsertExtraPadding())
return false;

for (auto *const Field : RD->fields()) {
if (!Field->getType().isBitwiseCloneableType(Context))
return false;
}

if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
for (auto Base : CXXRD->bases())
if (!Base.getType().isBitwiseCloneableType(Context))
Copy link
Contributor

@ilya-biryukov ilya-biryukov May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to note that because we aren't caching results of the computations for classes, the running time may end up expontential for some examples.

A synthetic example would be:

struct F1 { /*...*/ };
struct F2 : F1 {}
struct F3 : F1, F2 {}
struct F4 : F2, F3 {}
...

I don't think this would matter much in practice, but at least leaving a FIXME for the future would be useful.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, added a FIXME (we could use an extra bit in the class definition class to cache the result).

return false;
for (auto VBase : CXXRD->vbases())
if (!VBase.getType().isBitwiseCloneableType(Context))
return false;
}
return true;
}

bool QualType::isTriviallyCopyConstructibleType(
const ASTContext &Context) const {
return isTriviallyCopyableTypeImpl(*this, Context,
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Sema/SemaExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5126,6 +5126,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:
Expand Down Expand Up @@ -5619,6 +5620,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:
Expand Down
34 changes: 34 additions & 0 deletions clang/test/SemaCXX/builtin-is-bitwise-cloneable-fsanitize.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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;
};

union U {
S s;
};

struct Derived : S {};

static_assert(!__is_trivially_copyable(S));
#ifdef SANITIZER_ENABLED
// Don't allow memcpy when the struct has poisoned padding bits.
// The sanitizer adds posion padding bits to struct S.
static_assert(sizeof(S) > 16);
static_assert(!__is_bitwise_cloneable(S));
static_assert(sizeof(U) == sizeof(S)); // no padding bit for U.
static_assert(!__is_bitwise_cloneable(U));
static_assert(!__is_bitwise_cloneable(S[2]));
static_assert(!__is_bitwise_cloneable(Derived));
#else
static_assert(sizeof(S) == 16);
static_assert(__is_bitwise_cloneable(S));
static_assert(__is_bitwise_cloneable(U));
static_assert(__is_bitwise_cloneable(S[2]));
static_assert(__is_bitwise_cloneable(Derived));
#endif
8 changes: 8 additions & 0 deletions clang/test/SemaCXX/builtin-is-bitwise-cloneable.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s
//
struct DynamicClass { virtual int Foo(); };
static_assert(!__is_trivially_copyable(DynamicClass));
static_assert(__is_bitwise_cloneable(DynamicClass));

struct InComplete; // expected-note{{forward declaration}}
static_assert(!__is_bitwise_cloneable(InComplete)); // expected-error{{incomplete type 'InComplete' used in type trait expression}}
9 changes: 9 additions & 0 deletions clang/test/SemaObjCXX/arc-type-traits.mm
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,12 @@
TRAIT_IS_TRUE(__is_trivially_relocatable, HasStrong);
TRAIT_IS_FALSE(__is_trivially_relocatable, HasWeak);
TRAIT_IS_TRUE(__is_trivially_relocatable, HasUnsafeUnretained);

// __is_bitwise_cloneable
TRAIT_IS_FALSE(__is_bitwise_cloneable, __strong id);
TRAIT_IS_FALSE(__is_bitwise_cloneable, __weak id);
TRAIT_IS_FALSE(__is_bitwise_cloneable, __autoreleasing id);
TRAIT_IS_TRUE(__is_trivial, __unsafe_unretained id);
TRAIT_IS_FALSE(__is_bitwise_cloneable, HasStrong);
TRAIT_IS_FALSE(__is_bitwise_cloneable, HasWeak);
TRAIT_IS_TRUE(__is_bitwise_cloneable, HasUnsafeUnretained);
Loading