[clang] 70982ef - [clang] Add clang::preferred_type attribute for bitfields (#69104)

via cfe-commits cfe-commits at lists.llvm.org
Mon Oct 23 11:22:05 PDT 2023


Author: Vlad Serebrennikov
Date: 2023-10-23T22:22:00+04:00
New Revision: 70982ef9d17ce57909ab202dc0850182b29c7da1

URL: https://github.com/llvm/llvm-project/commit/70982ef9d17ce57909ab202dc0850182b29c7da1
DIFF: https://github.com/llvm/llvm-project/commit/70982ef9d17ce57909ab202dc0850182b29c7da1.diff

LOG: [clang] Add clang::preferred_type attribute for bitfields (#69104)

This attribute allows user to specify type of the bitfield that will be
emitted to debug info without affecting semantics of the program. Since
it doesn't affect semantics, this attribute can be safely ignored by
other compilers.

This is useful when user is forced to use the same type for all
bitfields in a class to get better
[layout](https://godbolt.org/z/ovWqzqv9x) and
[codegen](https://godbolt.org/z/bdoqvz9e6) from MSVC, because it allows
debuggers to interpret the value of bitfield in the most human-friendly
way (e.g. when value actually comes from an enum). This is driven by my
work on LLDB formatters for Clang. I have two use cases for this:
```cpp
namespace Clang {
class Type {
  enum TypeClass { ... };
  struct TypeBitfields {
    [[clang::preferred_type(clang::Type::TypeClass)]] unsigned TC: 8;
    [[clang::preferred_type(bool)]] mutable unsigned FromAST : 1;
  };
};
}
```

Added: 
    clang/test/CodeGen/debug-info-preferred-type.cpp
    clang/test/Sema/attr-preferred-type.cpp

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Basic/Attr.td
    clang/include/clang/Basic/AttrDocs.td
    clang/include/clang/Basic/DiagnosticGroups.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/CodeGen/CGDebugInfo.cpp
    clang/lib/Sema/SemaDeclAttr.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 403a74fc602fd81..88a91a9d52146c0 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -248,6 +248,24 @@ Attribute Changes in Clang
   supports but that are never the result of default argument promotion, such as
   ``float``. (`#59824: <https://github.com/llvm/llvm-project/issues/59824>`_)
 
+- Clang now supports ``[[clang::preferred_type(type-name)]]`` as an attribute
+  which can be applied to a bit-field. This attribute helps to map a bit-field
+  back to a particular type that may be better-suited to representing the bit-
+  field but cannot be used for other reasons and will impact the debug
+  information generated for the bit-field. This is most useful when mapping a
+  bit-field of basic integer type back to a ``bool`` or an enumeration type,
+  e.g.,
+
+  .. code-block:: c++
+
+      enum E { Apple, Orange, Pear };
+      struct S {
+        [[clang::preferred_type(E)]] unsigned FruitKind : 2;
+      };
+
+  When viewing ``S::FruitKind`` in a debugger, it will behave as if the member
+  was declared as type ``E`` rather than ``unsigned``.
+
 Improvements to Clang's diagnostics
 -----------------------------------
 - Clang constexpr evaluator now prints template arguments when displaying

diff  --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 5486b36133755cc..25231c5b82b907c 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -107,6 +107,10 @@ def NonBitField : SubsetSubject<Field,
                                 [{!S->isBitField()}],
                                 "non-bit-field non-static data members">;
 
+def BitField : SubsetSubject<Field,
+                             [{S->isBitField()}],
+                             "bit-field data members">;
+
 def NonStaticCXXMethod : SubsetSubject<CXXMethod,
                                        [{!S->isStatic()}],
                                        "non-static member functions">;
@@ -4269,3 +4273,10 @@ def CountedBy : InheritableAttr {
       void setCountedByFieldLoc(SourceRange Loc) { CountedByFieldLoc = Loc; }
   }];
 }
+
+def PreferredType: InheritableAttr {
+  let Spellings = [Clang<"preferred_type">];
+  let Subjects = SubjectList<[BitField], ErrorDiag>;
+  let Args = [TypeArgument<"Type", 1>];
+  let Documentation = [PreferredTypeDocumentation];
+}

diff  --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index cbbf69faeb308ad..05703df2129f612 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -7231,6 +7231,69 @@ its underlying representation to be a WebAssembly ``funcref``.
   }];
 }
 
+def PreferredTypeDocumentation : Documentation {
+  let Category = DocCatField;
+  let Content = [{
+This attribute allows adjusting the type of a bit-field in debug information.
+This can be helpful when a bit-field is intended to store an enumeration value,
+but has to be specified as having the enumeration's underlying type in order to
+facilitate compiler optimizations or bit-field packing behavior. Normally, the
+underlying type is what is emitted in debug information, which can make it hard
+for debuggers to know to map a bit-field's value back to a particular enumeration.
+
+.. code-block:: c++
+
+    enum Colors { Red, Green, Blue };
+
+    struct S {
+      [[clang::preferred_type(Colors)]] unsigned ColorVal : 2;
+      [[clang::preferred_type(bool)]] unsigned UseAlternateColorSpace : 1;
+    } s = { Green, false };
+
+Without the attribute, a debugger is likely to display the value ``1`` for ``ColorVal``
+and ``0`` for ``UseAlternateColorSpace``. With the attribute, the debugger may now
+display ``Green`` and ``false`` instead.
+
+This can be used to map a bit-field to an arbitrary type that isn't integral
+or an enumeration type. For example:
+
+.. code-block:: c++
+
+    struct A {
+      short a1;
+      short a2;
+    };
+
+    struct B {
+      [[clang::preferred_type(A)]] unsigned b1 : 32 = 0x000F'000C;
+    };
+
+will associate the type ``A`` with the ``b1`` bit-field and is intended to display
+something like this in the debugger:
+
+.. code-block:: text
+
+    Process 2755547 stopped
+    * thread #1, name = 'test-preferred-', stop reason = step in
+        frame #0: 0x0000555555555148 test-preferred-type`main at test.cxx:13:14
+       10   int main()
+       11   {
+       12       B b;
+    -> 13       return b.b1;
+       14   }
+    (lldb) v -T
+    (B) b = {
+      (A:32) b1 = {
+        (short) a1 = 12
+        (short) a2 = 15
+      }
+    }
+
+Note that debuggers may not be able to handle more complex mappings, and so
+this usage is debugger-dependent.
+  }];
+}
+
 def CleanupDocs : Documentation {
   let Category = DocCatType;
   let Content = [{

diff  --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index e990bf6b7938ac6..ee4383216bdccf7 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -54,6 +54,7 @@ def BitFieldConstantConversion : DiagGroup<"bitfield-constant-conversion",
                                            [SingleBitBitFieldConstantConversion]>;
 def BitFieldEnumConversion : DiagGroup<"bitfield-enum-conversion">;
 def BitFieldWidth : DiagGroup<"bitfield-width">;
+def BitFieldType : DiagGroup<"bitfield-type">;
 def CompoundTokenSplitByMacro : DiagGroup<"compound-token-split-by-macro">;
 def CompoundTokenSplitBySpace : DiagGroup<"compound-token-split-by-space">;
 def CompoundTokenSplit : DiagGroup<"compound-token-split",

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 88315f25d1dcd79..941034820d2ef20 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3165,6 +3165,9 @@ def err_invalid_branch_protection_spec : Error<
   "invalid or misplaced branch protection specification '%0'">;
 def warn_unsupported_branch_protection_spec : Warning<
   "unsupported branch protection specification '%0'">, InGroup<BranchProtection>;
+def warn_attribute_underlying_type_mismatch : Warning<
+  "underlying type %0 of enumeration %1 doesn't match bit-field type %2">,
+  InGroup<BitFieldType>;
 
 def warn_unsupported_target_attribute
     : Warning<"%select{unsupported|duplicate|unknown}0%select{| CPU|"

diff  --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index c430713b0d77d79..181e500b9671759 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -1499,6 +1499,8 @@ CGDebugInfo::createBitFieldType(const FieldDecl *BitFieldDecl,
                                 llvm::DIScope *RecordTy, const RecordDecl *RD) {
   StringRef Name = BitFieldDecl->getName();
   QualType Ty = BitFieldDecl->getType();
+  if (BitFieldDecl->hasAttr<PreferredTypeAttr>())
+    Ty = BitFieldDecl->getAttr<PreferredTypeAttr>()->getType();
   SourceLocation Loc = BitFieldDecl->getLocation();
   llvm::DIFile *VUnit = getOrCreateFile(Loc);
   llvm::DIType *DebugType = getOrCreateType(Ty, VUnit);

diff  --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 5adf058bea56a53..fc4e3ccf29a6051 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -5916,6 +5916,43 @@ static void handleBuiltinAliasAttr(Sema &S, Decl *D,
   D->addAttr(::new (S.Context) BuiltinAliasAttr(S.Context, AL, Ident));
 }
 
+static void handlePreferredTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+  if (!AL.hasParsedType()) {
+    S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << AL << 1;
+    return;
+  }
+
+  TypeSourceInfo *ParmTSI = nullptr;
+  QualType QT = S.GetTypeFromParser(AL.getTypeArg(), &ParmTSI);
+  assert(ParmTSI && "no type source info for attribute argument");
+  S.RequireCompleteType(ParmTSI->getTypeLoc().getBeginLoc(), QT,
+                        diag::err_incomplete_type);
+
+  if (QT->isEnumeralType()) {
+    auto IsCorrespondingType = [&](QualType LHS, QualType RHS) {
+      assert(LHS != RHS);
+      if (LHS->isSignedIntegerType())
+        return LHS == S.getASTContext().getCorrespondingSignedType(RHS);
+      return LHS == S.getASTContext().getCorrespondingUnsignedType(RHS);
+    };
+    QualType BitfieldType =
+        cast<FieldDecl>(D)->getType()->getCanonicalTypeUnqualified();
+    QualType EnumUnderlyingType = QT->getAs<EnumType>()
+                                      ->getDecl()
+                                      ->getIntegerType()
+                                      ->getCanonicalTypeUnqualified();
+    if (EnumUnderlyingType != BitfieldType &&
+        !IsCorrespondingType(EnumUnderlyingType, BitfieldType)) {
+      S.Diag(ParmTSI->getTypeLoc().getBeginLoc(),
+             diag::warn_attribute_underlying_type_mismatch)
+          << EnumUnderlyingType << QT << BitfieldType;
+      return;
+    }
+  }
+
+  D->addAttr(::new (S.Context) PreferredTypeAttr(S.Context, AL, ParmTSI));
+}
+
 //===----------------------------------------------------------------------===//
 // Checker-specific attribute handlers.
 //===----------------------------------------------------------------------===//
@@ -9636,6 +9673,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
     handleBuiltinAliasAttr(S, D, AL);
     break;
 
+  case ParsedAttr::AT_PreferredType:
+    handlePreferredTypeAttr(S, D, AL);
+    break;
+
   case ParsedAttr::AT_UsingIfExists:
     handleSimpleAttribute<UsingIfExistsAttr>(S, D, AL);
     break;

diff  --git a/clang/test/CodeGen/debug-info-preferred-type.cpp b/clang/test/CodeGen/debug-info-preferred-type.cpp
new file mode 100644
index 000000000000000..6406657a18e925c
--- /dev/null
+++ b/clang/test/CodeGen/debug-info-preferred-type.cpp
@@ -0,0 +1,9 @@
+// RUN: %clang -target x86_64-linux -g -S -emit-llvm -o - %s | FileCheck %s
+
+struct A {
+  enum E : unsigned {};
+  [[clang::preferred_type(E)]] unsigned b : 2;
+} a;
+
+// CHECK-DAG: [[ENUM:![0-9]+]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "E"{{.*}}
+// CHECK-DAG: !DIDerivedType(tag: DW_TAG_member, name: "b",{{.*}} baseType: [[ENUM]]

diff  --git a/clang/test/Sema/attr-preferred-type.cpp b/clang/test/Sema/attr-preferred-type.cpp
new file mode 100644
index 000000000000000..dfa7e636c21e94b
--- /dev/null
+++ b/clang/test/Sema/attr-preferred-type.cpp
@@ -0,0 +1,19 @@
+// RUN: %clang_cc1 -verify %s
+
+struct A {
+  enum E : unsigned {};
+  enum E2 : int {};
+  [[clang::preferred_type(E)]] unsigned b : 2;
+  [[clang::preferred_type(E)]] int b2 : 2;
+  [[clang::preferred_type(E2)]] const unsigned b3 : 2;
+  [[clang::preferred_type(bool)]] unsigned b4 : 1;
+  [[clang::preferred_type(bool)]] unsigned b5 : 2;
+  [[clang::preferred_type()]] unsigned b6 : 2;
+  // expected-error at -1 {{'preferred_type' attribute takes one argument}}
+  [[clang::preferred_type]] unsigned b7 : 2;
+  // expected-error at -1 {{'preferred_type' attribute takes one argument}}
+  [[clang::preferred_type(E, int)]] unsigned b8 : 2;
+  // expected-error at -1 {{expected ')'}}
+  // expected-error at -2 {{expected ','}}
+  // expected-warning at -3 {{unknown attribute 'int' ignored}}
+};


        


More information about the cfe-commits mailing list