Skip to content

Commit 70982ef

Browse files
authored
[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; }; }; } ```
1 parent 2973feb commit 70982ef

File tree

9 files changed

+167
-0
lines changed

9 files changed

+167
-0
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,24 @@ Attribute Changes in Clang
248248
supports but that are never the result of default argument promotion, such as
249249
``float``. (`#59824: <https://github.com/llvm/llvm-project/issues/59824>`_)
250250

251+
- Clang now supports ``[[clang::preferred_type(type-name)]]`` as an attribute
252+
which can be applied to a bit-field. This attribute helps to map a bit-field
253+
back to a particular type that may be better-suited to representing the bit-
254+
field but cannot be used for other reasons and will impact the debug
255+
information generated for the bit-field. This is most useful when mapping a
256+
bit-field of basic integer type back to a ``bool`` or an enumeration type,
257+
e.g.,
258+
259+
.. code-block:: c++
260+
261+
enum E { Apple, Orange, Pear };
262+
struct S {
263+
[[clang::preferred_type(E)]] unsigned FruitKind : 2;
264+
};
265+
266+
When viewing ``S::FruitKind`` in a debugger, it will behave as if the member
267+
was declared as type ``E`` rather than ``unsigned``.
268+
251269
Improvements to Clang's diagnostics
252270
-----------------------------------
253271
- Clang constexpr evaluator now prints template arguments when displaying

clang/include/clang/Basic/Attr.td

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ def NonBitField : SubsetSubject<Field,
107107
[{!S->isBitField()}],
108108
"non-bit-field non-static data members">;
109109

110+
def BitField : SubsetSubject<Field,
111+
[{S->isBitField()}],
112+
"bit-field data members">;
113+
110114
def NonStaticCXXMethod : SubsetSubject<CXXMethod,
111115
[{!S->isStatic()}],
112116
"non-static member functions">;
@@ -4269,3 +4273,10 @@ def CountedBy : InheritableAttr {
42694273
void setCountedByFieldLoc(SourceRange Loc) { CountedByFieldLoc = Loc; }
42704274
}];
42714275
}
4276+
4277+
def PreferredType: InheritableAttr {
4278+
let Spellings = [Clang<"preferred_type">];
4279+
let Subjects = SubjectList<[BitField], ErrorDiag>;
4280+
let Args = [TypeArgument<"Type", 1>];
4281+
let Documentation = [PreferredTypeDocumentation];
4282+
}

clang/include/clang/Basic/AttrDocs.td

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7231,6 +7231,69 @@ its underlying representation to be a WebAssembly ``funcref``.
72317231
}];
72327232
}
72337233

7234+
def PreferredTypeDocumentation : Documentation {
7235+
let Category = DocCatField;
7236+
let Content = [{
7237+
This attribute allows adjusting the type of a bit-field in debug information.
7238+
This can be helpful when a bit-field is intended to store an enumeration value,
7239+
but has to be specified as having the enumeration's underlying type in order to
7240+
facilitate compiler optimizations or bit-field packing behavior. Normally, the
7241+
underlying type is what is emitted in debug information, which can make it hard
7242+
for debuggers to know to map a bit-field's value back to a particular enumeration.
7243+
7244+
.. code-block:: c++
7245+
7246+
enum Colors { Red, Green, Blue };
7247+
7248+
struct S {
7249+
[[clang::preferred_type(Colors)]] unsigned ColorVal : 2;
7250+
[[clang::preferred_type(bool)]] unsigned UseAlternateColorSpace : 1;
7251+
} s = { Green, false };
7252+
7253+
Without the attribute, a debugger is likely to display the value ``1`` for ``ColorVal``
7254+
and ``0`` for ``UseAlternateColorSpace``. With the attribute, the debugger may now
7255+
display ``Green`` and ``false`` instead.
7256+
7257+
This can be used to map a bit-field to an arbitrary type that isn't integral
7258+
or an enumeration type. For example:
7259+
7260+
.. code-block:: c++
7261+
7262+
struct A {
7263+
short a1;
7264+
short a2;
7265+
};
7266+
7267+
struct B {
7268+
[[clang::preferred_type(A)]] unsigned b1 : 32 = 0x000F'000C;
7269+
};
7270+
7271+
will associate the type ``A`` with the ``b1`` bit-field and is intended to display
7272+
something like this in the debugger:
7273+
7274+
.. code-block:: text
7275+
7276+
Process 2755547 stopped
7277+
* thread #1, name = 'test-preferred-', stop reason = step in
7278+
frame #0: 0x0000555555555148 test-preferred-type`main at test.cxx:13:14
7279+
10 int main()
7280+
11 {
7281+
12 B b;
7282+
-> 13 return b.b1;
7283+
14 }
7284+
(lldb) v -T
7285+
(B) b = {
7286+
(A:32) b1 = {
7287+
(short) a1 = 12
7288+
(short) a2 = 15
7289+
}
7290+
}
7291+
7292+
Note that debuggers may not be able to handle more complex mappings, and so
7293+
this usage is debugger-dependent.
7294+
}];
7295+
}
7296+
72347297
def CleanupDocs : Documentation {
72357298
let Category = DocCatType;
72367299
let Content = [{

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def BitFieldConstantConversion : DiagGroup<"bitfield-constant-conversion",
5454
[SingleBitBitFieldConstantConversion]>;
5555
def BitFieldEnumConversion : DiagGroup<"bitfield-enum-conversion">;
5656
def BitFieldWidth : DiagGroup<"bitfield-width">;
57+
def BitFieldType : DiagGroup<"bitfield-type">;
5758
def CompoundTokenSplitByMacro : DiagGroup<"compound-token-split-by-macro">;
5859
def CompoundTokenSplitBySpace : DiagGroup<"compound-token-split-by-space">;
5960
def CompoundTokenSplit : DiagGroup<"compound-token-split",

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3165,6 +3165,9 @@ def err_invalid_branch_protection_spec : Error<
31653165
"invalid or misplaced branch protection specification '%0'">;
31663166
def warn_unsupported_branch_protection_spec : Warning<
31673167
"unsupported branch protection specification '%0'">, InGroup<BranchProtection>;
3168+
def warn_attribute_underlying_type_mismatch : Warning<
3169+
"underlying type %0 of enumeration %1 doesn't match bit-field type %2">,
3170+
InGroup<BitFieldType>;
31683171

31693172
def warn_unsupported_target_attribute
31703173
: Warning<"%select{unsupported|duplicate|unknown}0%select{| CPU|"

clang/lib/CodeGen/CGDebugInfo.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,8 @@ CGDebugInfo::createBitFieldType(const FieldDecl *BitFieldDecl,
14991499
llvm::DIScope *RecordTy, const RecordDecl *RD) {
15001500
StringRef Name = BitFieldDecl->getName();
15011501
QualType Ty = BitFieldDecl->getType();
1502+
if (BitFieldDecl->hasAttr<PreferredTypeAttr>())
1503+
Ty = BitFieldDecl->getAttr<PreferredTypeAttr>()->getType();
15021504
SourceLocation Loc = BitFieldDecl->getLocation();
15031505
llvm::DIFile *VUnit = getOrCreateFile(Loc);
15041506
llvm::DIType *DebugType = getOrCreateType(Ty, VUnit);

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5916,6 +5916,43 @@ static void handleBuiltinAliasAttr(Sema &S, Decl *D,
59165916
D->addAttr(::new (S.Context) BuiltinAliasAttr(S.Context, AL, Ident));
59175917
}
59185918

5919+
static void handlePreferredTypeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
5920+
if (!AL.hasParsedType()) {
5921+
S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << AL << 1;
5922+
return;
5923+
}
5924+
5925+
TypeSourceInfo *ParmTSI = nullptr;
5926+
QualType QT = S.GetTypeFromParser(AL.getTypeArg(), &ParmTSI);
5927+
assert(ParmTSI && "no type source info for attribute argument");
5928+
S.RequireCompleteType(ParmTSI->getTypeLoc().getBeginLoc(), QT,
5929+
diag::err_incomplete_type);
5930+
5931+
if (QT->isEnumeralType()) {
5932+
auto IsCorrespondingType = [&](QualType LHS, QualType RHS) {
5933+
assert(LHS != RHS);
5934+
if (LHS->isSignedIntegerType())
5935+
return LHS == S.getASTContext().getCorrespondingSignedType(RHS);
5936+
return LHS == S.getASTContext().getCorrespondingUnsignedType(RHS);
5937+
};
5938+
QualType BitfieldType =
5939+
cast<FieldDecl>(D)->getType()->getCanonicalTypeUnqualified();
5940+
QualType EnumUnderlyingType = QT->getAs<EnumType>()
5941+
->getDecl()
5942+
->getIntegerType()
5943+
->getCanonicalTypeUnqualified();
5944+
if (EnumUnderlyingType != BitfieldType &&
5945+
!IsCorrespondingType(EnumUnderlyingType, BitfieldType)) {
5946+
S.Diag(ParmTSI->getTypeLoc().getBeginLoc(),
5947+
diag::warn_attribute_underlying_type_mismatch)
5948+
<< EnumUnderlyingType << QT << BitfieldType;
5949+
return;
5950+
}
5951+
}
5952+
5953+
D->addAttr(::new (S.Context) PreferredTypeAttr(S.Context, AL, ParmTSI));
5954+
}
5955+
59195956
//===----------------------------------------------------------------------===//
59205957
// Checker-specific attribute handlers.
59215958
//===----------------------------------------------------------------------===//
@@ -9636,6 +9673,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
96369673
handleBuiltinAliasAttr(S, D, AL);
96379674
break;
96389675

9676+
case ParsedAttr::AT_PreferredType:
9677+
handlePreferredTypeAttr(S, D, AL);
9678+
break;
9679+
96399680
case ParsedAttr::AT_UsingIfExists:
96409681
handleSimpleAttribute<UsingIfExistsAttr>(S, D, AL);
96419682
break;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// RUN: %clang -target x86_64-linux -g -S -emit-llvm -o - %s | FileCheck %s
2+
3+
struct A {
4+
enum E : unsigned {};
5+
[[clang::preferred_type(E)]] unsigned b : 2;
6+
} a;
7+
8+
// CHECK-DAG: [[ENUM:![0-9]+]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "E"{{.*}}
9+
// CHECK-DAG: !DIDerivedType(tag: DW_TAG_member, name: "b",{{.*}} baseType: [[ENUM]]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// RUN: %clang_cc1 -verify %s
2+
3+
struct A {
4+
enum E : unsigned {};
5+
enum E2 : int {};
6+
[[clang::preferred_type(E)]] unsigned b : 2;
7+
[[clang::preferred_type(E)]] int b2 : 2;
8+
[[clang::preferred_type(E2)]] const unsigned b3 : 2;
9+
[[clang::preferred_type(bool)]] unsigned b4 : 1;
10+
[[clang::preferred_type(bool)]] unsigned b5 : 2;
11+
[[clang::preferred_type()]] unsigned b6 : 2;
12+
// expected-error@-1 {{'preferred_type' attribute takes one argument}}
13+
[[clang::preferred_type]] unsigned b7 : 2;
14+
// expected-error@-1 {{'preferred_type' attribute takes one argument}}
15+
[[clang::preferred_type(E, int)]] unsigned b8 : 2;
16+
// expected-error@-1 {{expected ')'}}
17+
// expected-error@-2 {{expected ','}}
18+
// expected-warning@-3 {{unknown attribute 'int' ignored}}
19+
};

0 commit comments

Comments
 (0)