Skip to content

Conversation

@Endilll
Copy link
Contributor

@Endilll Endilll commented Feb 12, 2024

This patch implements __is_layout_compatible intrinsic, which backs up std::is_layout_compatible type trait introduced in C++20 (P0466R5 "Layout-compatibility and Pointer-interconvertibility Traits"). Name matched GCC and MSVC intrinsics.

Basically, this patch exposes our existing machinery for checking for layout compatibility and figuring out common initial sequences. Said machinery is a bit outdated, as it doesn't implement CWG1719 "Layout compatibility and cv-qualification revisited" and CWG2759 "[[no_unique_address] and common initial sequence". Those defect reports are considered out of scope of of this PR, but will be implemented in subsequent PRs.

Partially addresses #48204

@Endilll Endilll added the c++20 label Feb 12, 2024
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Feb 12, 2024
@llvmbot
Copy link
Member

llvmbot commented Feb 12, 2024

@llvm/pr-subscribers-clang

Author: Vlad Serebrennikov (Endilll)

Changes

This patch implements __is_layout_compatible, which backs up std::is_layout_compatible type trait introduced in C++20 (P0466R5 "Layout-compatibility and Pointer-interconvertibility Traits"). Name matched GCC and MSVC intrinsics.

Basically, this patch exposes our existing machinery for checking for layout compatibility and figuring out common initial sequences. Said machinery is a bit outdated, as it doesn't implement CWG1719 "Layout compatibility and cv-qualification revisited" and CWG2759 "[[no_unique_address] and common initial sequence". Those defect reports are considered out of scope of of this PR, but will be implemented in subsequent PRs.

Partially addresses #48204


Full diff: https://github.com/llvm/llvm-project/pull/81506.diff

7 Files Affected:

  • (modified) clang/include/clang/Basic/TokenKinds.def (+1)
  • (modified) clang/include/clang/Sema/Sema.h (+2)
  • (modified) clang/lib/Parse/ParseDeclCXX.cpp (+1)
  • (modified) clang/lib/Parse/ParseExpr.cpp (+1)
  • (modified) clang/lib/Sema/SemaChecking.cpp (+4)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+3)
  • (modified) clang/test/SemaCXX/type-traits.cpp (+83)
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 23817cde7a9354..112bfe8b23c2c0 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -520,6 +520,7 @@ TYPE_TRAIT_1(__is_trivially_copyable, IsTriviallyCopyable, KEYCXX)
 TYPE_TRAIT_1(__is_union, IsUnion, KEYCXX)
 TYPE_TRAIT_1(__has_unique_object_representations,
              HasUniqueObjectRepresentations, KEYCXX)
+TYPE_TRAIT_2(__is_layout_compatible, IsLayoutCompatible, KEYCXX)
 
 #define TRANSFORM_TYPE_TRAIT_DEF(_, Trait) KEYWORD(__##Trait, KEYCXX)
 #include "clang/Basic/TransformTypeTraits.def"
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 851560f759f0e4..ddeba328749653 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -14040,6 +14040,8 @@ class Sema final {
   bool SemaValueIsRunOfOnes(CallExpr *TheCall, unsigned ArgNum);
 
 public:
+  bool SemaIsLayoutCompatible(QualType T1, QualType T2);
+  
   // Used by C++ template instantiation.
   ExprResult SemaBuiltinShuffleVector(CallExpr *TheCall);
   ExprResult SemaConvertVectorExpr(Expr *E, TypeSourceInfo *TInfo,
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 79928ddb5af599..c9360981c1c1e4 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1717,6 +1717,7 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind,
           tok::kw___is_fundamental,
           tok::kw___is_integral,
           tok::kw___is_interface_class,
+          tok::kw___is_layout_compatible,
           tok::kw___is_literal,
           tok::kw___is_lvalue_expr,
           tok::kw___is_lvalue_reference,
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 52cebdb6f64bac..db21a7f1e9e368 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -1114,6 +1114,7 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind,
           REVERTIBLE_TYPE_TRAIT(__is_fundamental);
           REVERTIBLE_TYPE_TRAIT(__is_integral);
           REVERTIBLE_TYPE_TRAIT(__is_interface_class);
+          REVERTIBLE_TYPE_TRAIT(__is_layout_compatible);
           REVERTIBLE_TYPE_TRAIT(__is_literal);
           REVERTIBLE_TYPE_TRAIT(__is_lvalue_expr);
           REVERTIBLE_TYPE_TRAIT(__is_lvalue_reference);
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 71e6e7230fc455..8aa744873f3704 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -19150,6 +19150,10 @@ static bool isLayoutCompatible(ASTContext &C, QualType T1, QualType T2) {
   return false;
 }
 
+bool Sema::SemaIsLayoutCompatible(QualType T1, QualType T2) {
+  return isLayoutCompatible(getASTContext(), T1, T2);
+}
+
 //===--- CHECK: pointer_with_type_tag attribute: datatypes should match ----//
 
 /// Given a type tag expression find the type tag itself.
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 246d2313e089f3..b279c367342fce 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5922,6 +5922,9 @@ static bool EvaluateBinaryTypeTrait(Sema &Self, TypeTrait BTT, QualType LhsT,
 
     llvm_unreachable("unhandled type trait");
     return false;
+  }
+  case BTT_IsLayoutCompatible: {
+    return Self.SemaIsLayoutCompatible(LhsT, RhsT);
   }
     default: llvm_unreachable("not a BTT");
   }
diff --git a/clang/test/SemaCXX/type-traits.cpp b/clang/test/SemaCXX/type-traits.cpp
index 5659594577111e..51592849e97edd 100644
--- a/clang/test/SemaCXX/type-traits.cpp
+++ b/clang/test/SemaCXX/type-traits.cpp
@@ -1558,6 +1558,89 @@ void is_standard_layout()
   int t71[F(__is_standard_layout(HasEmptyIndirectBaseAsSecondUnionMember))];
 }
 
+struct CStruct2 {
+  int one;
+  int two;
+};
+
+struct CEmptyStruct2 {};
+
+struct CppEmptyStruct2 : CStruct2 {};
+struct CppStructStandard2 : CEmptyStruct2 {
+  int three;
+  int four;
+};
+struct CppStructNonStandardByBase2 : CStruct2 {
+  int three;
+  int four;
+};
+struct CppStructNonStandardByVirt2 : CStruct2 {
+  virtual void method() {}
+};
+struct CppStructNonStandardByMemb2 : CStruct2 {
+  CppStructNonStandardByVirt member;
+};
+struct CppStructNonStandardByProt2 : CStruct2 {
+  int five;
+protected:
+  int six;
+};
+struct CppStructNonStandardByVirtBase2 : virtual CStruct2 {
+};
+struct CppStructNonStandardBySameBase2 : CEmptyStruct2 {
+  CEmptyStruct member;
+};
+struct CppStructNonStandardBy2ndVirtBase2 : CEmptyStruct2 {
+  CEmptyStruct member;
+};
+
+struct CStructWithQualifiers {
+  const int one;
+  volatile int two;
+};
+
+struct CStructNoUniqueAddress {
+  int one;
+  [[no_unique_address]] int two;
+};
+
+struct CStructNoUniqueAddress2 {
+  int one;
+  [[no_unique_address]] int two;
+};
+
+struct CStructAlignment {
+  int one;
+  alignas(16) int two;
+};
+
+struct CStructIncomplete;
+
+void is_layout_compatible()
+{
+  static_assert(__is_layout_compatible(void, void), "");
+  static_assert(__is_layout_compatible(int, int), "");
+  static_assert(__is_layout_compatible(int[], int[]), "");
+  static_assert(__is_layout_compatible(int[2], int[2]), "");
+  static_assert(__is_layout_compatible(CStruct, CStruct2), "");
+  static_assert(__is_layout_compatible(CEmptyStruct, CEmptyStruct2), "");
+  static_assert(__is_layout_compatible(CppEmptyStruct, CppEmptyStruct2), "");
+  static_assert(__is_layout_compatible(CppStructStandard, CppStructStandard2), "");
+  static_assert(!__is_layout_compatible(CppStructNonStandardByBase, CppStructNonStandardByBase2), "");
+  static_assert(!__is_layout_compatible(CppStructNonStandardByVirt, CppStructNonStandardByVirt2), "");
+  static_assert(!__is_layout_compatible(CppStructNonStandardByMemb, CppStructNonStandardByMemb2), "");
+  static_assert(!__is_layout_compatible(CppStructNonStandardByProt, CppStructNonStandardByProt2), "");
+  static_assert(!__is_layout_compatible(CppStructNonStandardByVirtBase, CppStructNonStandardByVirtBase2), "");
+  static_assert(!__is_layout_compatible(CppStructNonStandardBySameBase, CppStructNonStandardBySameBase2), "");
+  static_assert(!__is_layout_compatible(CppStructNonStandardBy2ndVirtBase, CppStructNonStandardBy2ndVirtBase2), "");
+  static_assert(!__is_layout_compatible(CStruct, CStructWithQualifiers), ""); // FIXME: this is CWG1719
+  static_assert(__is_layout_compatible(CStruct, CStructNoUniqueAddress) == bool(__has_cpp_attribute(no_unique_address)), ""); // FIXME: this is CWG2759
+  static_assert(__is_layout_compatible(CStructNoUniqueAddress, CStructNoUniqueAddress2) == bool(__has_cpp_attribute(no_unique_address)), ""); // FIXME: this is CWG2759
+  static_assert(__is_layout_compatible(CStruct, CStructAlignment), "");
+  static_assert(__is_layout_compatible(CStructIncomplete, CStructIncomplete), "");
+  static_assert(!__is_layout_compatible(CStruct, CStructIncomplete), "");
+}
+
 void is_signed()
 {
   //int t01[T(__is_signed(char))];

@github-actions
Copy link

github-actions bot commented Feb 12, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@Endilll
Copy link
Contributor Author

Endilll commented Feb 12, 2024

Formatting suggestions to ParseDeclCXX.cpp are too disruptive. I'm not going to commit them.

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

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

This looks good generally.
Can you add a changelog entry?
Can you add some doc in LanguageExtension.rst?

Thanks

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

Thank you for this! Please be sure to also add a release note and documentation (https://clang.llvm.org/docs/LanguageExtensions.html#type-trait-primitives).

static_assert(__is_layout_compatible(CStruct, CStructAlignment), "");
static_assert(__is_layout_compatible(CStructIncomplete, CStructIncomplete), "");
static_assert(!__is_layout_compatible(CStruct, CStructIncomplete), "");
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Some additional test cases to consider:

  • A type is layout compatible with its qualified version. e.g., SomeStruct and const SomeStruct
  • Layout compatibility of function types (not function pointer types), e.g., void(int) and void(int)
  • Diagnostics when given an incomplete type for either operand
  • That int and unsigned int are not layout compatible
  • That char and signed char/unsigned char are not layout compatible
  • That an enumeration type and its underlying type are not layout compatible

If this catches bugs in the implementation of isLayoutCompatible(), that' fine, we can document the current behavior in the test with a FIXME and come back to addressing the issues in a follow-up.

Copy link
Contributor

Choose a reason for hiding this comment

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

  • A type is layout compatible with its qualified version. e.g., SomeStruct and const SomeStruct

We do not yet implement https://cplusplus.github.io/CWG/issues/1719.html, so some tests are better deferred to the implementation of that DR

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should add those tests somewhere (either here or in dr1xxx.cpp) so 1) we know we don't crash on them, 2) we're alerted to behavioral changes in this area, and 3) better documentation of existing behavior.

Copy link
Contributor Author

@Endilll Endilll Feb 16, 2024

Choose a reason for hiding this comment

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

A type is layout compatible with its qualified version. e.g., SomeStruct and const SomeStruct

Done. It even works correctly for class types.

Layout compatibility of function types (not function pointer types), e.g., void(int) and void(int)

Done. I added tests for function types, reference to functions, pointers to function, pointers to data members, pointers to member functions.

Diagnostics when given an incomplete type for either operand

We don't issue any, as far as I can see. And I don't think we can when the types are the same, ignoring cv qualifiers:

Two types cv1 T1 and cv2 T2 are layout-compatible types if T1 and T2 are the same type, layout-compatible enumerations, or layout-compatible standard-layout class types.

That int and unsigned int are not layout compatible
That char and signed char/unsigned char are not layout compatible
That an enumeration type and its underlying type are not layout compatible

Done.

I think we should add those tests somewhere (either here or in dr1xxx.cpp) so 1) we know we don't crash on them, 2) we're alerted to behavioral changes in this area, and 3) better documentation of existing behavior.

Agreed. I marked 1334 as superseded by 1719, and added a test for 1719.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't issue any, as far as I can see. And I don't think we can when the types are the same, ignoring cv qualifiers:

That's a bug, see https://eel.is/c++draft/tab:meta.rel#row-6-column-3-sentence-1: "T and U shall be complete types, cv void, or arrays of unknown bound."

but also: https://eel.is/c++draft/tab:meta.rel#row-6-column-3-sentence-1and http://eel.is/c++draft/class.mem#general-23 -- you can't test for layout compatibility if the class type is not complete because you don't know the members. So we should accept void but not struct NeverDefined.

Copy link
Contributor

Choose a reason for hiding this comment

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

  • That an enumeration type and its underlying type are not layout compatible

This is possibly a defect (cplusplus/CWG#39 (comment), cplusplus/CWG#95 (comment)), but no CWG issue is filed yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for mentioning that! I left a comment around enum tests.

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

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

LGTM!

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

LGTM!

@Endilll Endilll merged commit d5922cf into llvm:main Feb 20, 2024
@Endilll Endilll deleted the layout-compatible branch February 20, 2024 12:55
Endilll added a commit to Endilll/llvm-project that referenced this pull request Apr 5, 2024
This is a follow-up to llvm#81506. Since `__is_layout_compatible()` is a C++ intrinsic (https://github.com/llvm/llvm-project/blob/ff1e72d68d1224271801ff5192a8c14fbd3be83b/clang/include/clang/Basic/TokenKinds.def#L523), I don't think we should define how it interacts with VLA extension unless we have a compelling reason to.

Since llvm#81506 was merged after 18 cut-off, we don't have to follow any kind of deprecation process.
Endilll added a commit that referenced this pull request Apr 6, 2024
This is a follow-up to #81506. Since `__is_layout_compatible()` is a C++
intrinsic
(https://github.com/llvm/llvm-project/blob/ff1e72d68d1224271801ff5192a8c14fbd3be83b/clang/include/clang/Basic/TokenKinds.def#L523),
I don't think we should define how it interacts with VLA extension
unless we have a compelling reason to.

Since #81506 was merged after 18 cut-off, we don't have to follow any
kind of deprecation process.
Endilll added a commit to Endilll/llvm-project that referenced this pull request Apr 6, 2024
This is a follow-up to llvm#81506. As discussed in llvm#87737, we're rejecting incomplete types, save for exceptions listed in the C++ standard (`void` and arrays of unknown bound). Note that arrays of unknown bound of incomplete types are accepted.

Since we're happy with the current behavior of this intrinsic for flexible array members (llvm#87737 (comment)), I added a couple of tests for that as well.
Endilll added a commit that referenced this pull request Apr 8, 2024
This is a follow-up to #81506. As discussed in #87737, we're rejecting
incomplete types, save for exceptions listed in the C++ standard (`void`
and arrays of unknown bound). Note that arrays of unknown bound of
incomplete types are accepted.

Since we're happy with the current behavior of this intrinsic for
flexible array members
(#87737 (comment)),
I added a couple of tests for that as well.
Endilll added a commit that referenced this pull request Jul 25, 2024
…ist (#100572)

`__is_layout_compatible` was added in Clang 19 (#81506), and at that
time it wasn't entirely clear whether it should be a revertible type
trait or not. We decided to follow the example of other type traits.
Since then #95969 happened, and now we know that we don't want new
revertible type traits.

This patch removes `__is_layout_compatible` from revertible type traits
list, and leaves a comment what revertible type traits are, and that new
type traits should not be added there.

The intention is to also cherry-pick this to 19 branch.
llvmbot pushed a commit to llvmbot/llvm-project that referenced this pull request Jul 25, 2024
…ist (llvm#100572)

`__is_layout_compatible` was added in Clang 19 (llvm#81506), and at that
time it wasn't entirely clear whether it should be a revertible type
trait or not. We decided to follow the example of other type traits.
Since then llvm#95969 happened, and now we know that we don't want new
revertible type traits.

This patch removes `__is_layout_compatible` from revertible type traits
list, and leaves a comment what revertible type traits are, and that new
type traits should not be added there.

The intention is to also cherry-pick this to 19 branch.

(cherry picked from commit 3295d37)
yuxuanchen1997 pushed a commit that referenced this pull request Jul 25, 2024
…ist (#100572)

Summary:
`__is_layout_compatible` was added in Clang 19 (#81506), and at that
time it wasn't entirely clear whether it should be a revertible type
trait or not. We decided to follow the example of other type traits.
Since then #95969 happened, and now we know that we don't want new
revertible type traits.

This patch removes `__is_layout_compatible` from revertible type traits
list, and leaves a comment what revertible type traits are, and that new
type traits should not be added there.

The intention is to also cherry-pick this to 19 branch.

Test Plan: 

Reviewers: 

Subscribers: 

Tasks: 

Tags: 


Differential Revision: https://phabricator.intern.facebook.com/D60250602
tru pushed a commit to llvmbot/llvm-project that referenced this pull request Jul 26, 2024
…ist (llvm#100572)

`__is_layout_compatible` was added in Clang 19 (llvm#81506), and at that
time it wasn't entirely clear whether it should be a revertible type
trait or not. We decided to follow the example of other type traits.
Since then llvm#95969 happened, and now we know that we don't want new
revertible type traits.

This patch removes `__is_layout_compatible` from revertible type traits
list, and leaves a comment what revertible type traits are, and that new
type traits should not be added there.

The intention is to also cherry-pick this to 19 branch.

(cherry picked from commit 3295d37)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++20 clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants