Skip to content

[clang] Add clang::preferred_type attribute for bitfields #69104

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 14 commits into from
Oct 23, 2023
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
18 changes: 18 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,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
Expand Down
11 changes: 11 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -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">;
Expand Down Expand Up @@ -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];
}
63 changes: 63 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [{
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/DiagnosticGroups.td
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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<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|"
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/CodeGen/CGDebugInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<PreferredTypeAttr>())
Ty = BitFieldDecl->getAttr<PreferredTypeAttr>()->getType();
SourceLocation Loc = BitFieldDecl->getLocation();
llvm::DIFile *VUnit = getOrCreateFile(Loc);
llvm::DIType *DebugType = getOrCreateType(Ty, VUnit);
Expand Down
41 changes: 41 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<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.
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -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<UsingIfExistsAttr>(S, D, AL);
break;
Expand Down
9 changes: 9 additions & 0 deletions clang/test/CodeGen/debug-info-preferred-type.cpp
Original file line number Diff line number Diff line change
@@ -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]]
19 changes: 19 additions & 0 deletions clang/test/Sema/attr-preferred-type.cpp
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@AaronBallman On the topic of automatically-generated diagnostics:

/home/user/endill/llvm-project/clang/test/Sema/attr-preferred-type.cpp:16:28: error: expected ')'
   16 |   [[clang::preferred_type(E, int)]] unsigned b8 : 2;
      |                            ^
      |                            )
/home/user/endill/llvm-project/clang/test/Sema/attr-preferred-type.cpp:16:33: error: expected ','
   16 |   [[clang::preferred_type(E, int)]] unsigned b8 : 2;
      |                                 ^
      |                                 ,
/home/user/endill/llvm-project/clang/test/Sema/attr-preferred-type.cpp:16:30: warning: unknown attribute 'int' ignored [-Wunknown-attributes]
   16 |   [[clang::preferred_type(E, int)]] unsigned b8 : 2;
      |                              ^~~

3 diagnostics are issued, 0 says that wrong number of attribute arguments is passed.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Huh, something got confused there.... it's parsing int as though it were another attribute and not an argument to the preferred_type attribute. That surprises me!

// expected-error@-1 {{expected ')'}}
// expected-error@-2 {{expected ','}}
// expected-warning@-3 {{unknown attribute 'int' ignored}}
};