diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index be7c8bf247f7a..01a28944cfeb8 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -227,6 +227,24 @@ Attribute Changes in Clang supports but that are never the result of default argument promotion, such as ``float``. (`#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 5c9eb7b8a9810..867a31495cbc3 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -107,6 +107,10 @@ def NonBitField : SubsetSubjectisBitField()}], "non-bit-field non-static data members">; +def BitField : SubsetSubjectisBitField()}], + "bit-field data members">; + def NonStaticCXXMethod : SubsetSubjectisStatic()}], "non-static member functions">; @@ -4264,3 +4268,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 9f9991bdae361..3327182b54bc7 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -7219,6 +7219,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 0b09c00219184..381a674736b31 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 e85cd4d1a1ddc..24e001dadc38a 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3153,6 +3153,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; +def warn_attribute_underlying_type_mismatch : Warning< + "underlying type %0 of enumeration %1 doesn't match bit-field type %2">, + InGroup; 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 c73a63e12f03a..e6a751c4e297a 100644 --- a/clang/lib/CodeGen/CGDebugInfo.cpp +++ b/clang/lib/CodeGen/CGDebugInfo.cpp @@ -1497,6 +1497,8 @@ CGDebugInfo::createBitFieldType(const FieldDecl *BitFieldDecl, llvm::DIScope *RecordTy, const RecordDecl *RD) { StringRef Name = BitFieldDecl->getName(); QualType Ty = BitFieldDecl->getType(); + if (BitFieldDecl->hasAttr()) + Ty = BitFieldDecl->getAttr()->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 feb02cad9080e..4aac9139c20e8 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -5910,6 +5910,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(D)->getType()->getCanonicalTypeUnqualified(); + QualType EnumUnderlyingType = QT->getAs() + ->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. //===----------------------------------------------------------------------===// @@ -9629,6 +9666,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(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 0000000000000..6406657a18e92 --- /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 0000000000000..dfa7e636c21e9 --- /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@-1 {{'preferred_type' attribute takes one argument}} + [[clang::preferred_type]] unsigned b7 : 2; + // expected-error@-1 {{'preferred_type' attribute takes one argument}} + [[clang::preferred_type(E, int)]] unsigned b8 : 2; + // expected-error@-1 {{expected ')'}} + // expected-error@-2 {{expected ','}} + // expected-warning@-3 {{unknown attribute 'int' ignored}} +};