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

Conversation

Endilll
Copy link
Contributor

@Endilll Endilll commented Oct 15, 2023

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 and codegen 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:

namespace Clang {
class Type {
  enum TypeClass { ... };
  struct TypeBitfields {
    [[clang::debug_info_type(clang::Type::TypeClass)]] unsigned TC: 8;
    [[clang::debug_info_type(bool)]] mutable unsigned FromAST : 1;
  };
};
}

@Endilll Endilll added clang:frontend Language frontend issues, e.g. anything involving "Sema" debuginfo extension:clang labels Oct 15, 2023
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:codegen IR generation bugs: mangling, exceptions, etc. labels Oct 15, 2023
@llvmbot
Copy link
Member

llvmbot commented Oct 15, 2023

@llvm/pr-subscribers-clang
@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-debuginfo

Author: Vlad Serebrennikov (Endilll)

Changes

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 and codegen 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:

namespace Clang {
class Type {
  enum TypeClass { ... };
  struct TypeBitfields {
    [[clang::debug_info_type(clang::Type::TypeClass)]] unsigned TC: 8;
    [[clang::debug_info_type(bool)]] mutable unsigned FromAST : 1;
  };
};
}

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

6 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+11)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+12)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+2)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+26)
  • (added) clang/test/CodeGen/debug-info-debug-info-type.cpp (+14)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 5c9eb7b8a981037..024421c0583c019 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 non-static data members">;
+
 def NonStaticCXXMethod : SubsetSubject<CXXMethod,
                                        [{!S->isStatic()}],
                                        "non-static member functions">;
@@ -4264,3 +4268,10 @@ def CountedBy : InheritableAttr {
       void setCountedByFieldLoc(SourceRange Loc) { CountedByFieldLoc = Loc; }
   }];
 }
+
+def DebugInfoType: InheritableAttr {
+  let Spellings = [Clang<"debug_info_type">];
+  let Subjects = SubjectList<[BitField], ErrorDiag>;
+  let Args = [TypeArgument<"Type", 1>];
+  let Documentation = [DebugInfoTypeDocumentation];
+}
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 9f9991bdae36155..6cceba1e0e0ad01 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -7219,6 +7219,18 @@ its underlying representation to be a WebAssembly ``funcref``.
   }];
 }
 
+def DebugInfoTypeDocumentation : Documentation {
+  let Category = DocCatField;
+  let Content = [{
+This attribute allows to alter type of a bitfield in debug information.
+Such a need might arise when bitfield is intended to store an enumeration value,
+but has to be specified as having enumeration's underlying type, in order to
+facilitate compiler optimizations. But this also causes underlying type to be
+emitted in debug information, making it hard for debuggers to map bitfield's
+value back to enumeration. This attribute helps with this.
+  }];
+}
+
 def CleanupDocs : Documentation {
   let Category = DocCatType;
   let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index e85cd4d1a1ddc0d..b5c73494df367a6 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3153,6 +3153,8 @@ 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 bitfield type %2">;
 
 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 c73a63e12f03aab..85aedd87b21d41e 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<DebugInfoTypeAttr>())
+    Ty = BitFieldDecl->getAttr<DebugInfoTypeAttr>()->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 feb02cad9080e3e..8d58968b7f985c8 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -5910,6 +5910,28 @@ static void handleBuiltinAliasAttr(Sema &S, Decl *D,
   D->addAttr(::new (S.Context) BuiltinAliasAttr(S.Context, AL, Ident));
 }
 
+static void handleDebugInfoTypeAttr(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 type = S.GetTypeFromParser(AL.getTypeArg(), &ParmTSI);
+  assert(ParmTSI && "no type source info for attribute argument");
+
+  if (type->isEnumeralType()) {
+    QualType BitfieldType = llvm::cast<FieldDecl>(D)->getType();
+    QualType EnumUnderlyingType = type->getAs<EnumType>()->getDecl()->getIntegerType();
+    if (EnumUnderlyingType != BitfieldType) {
+      S.Diag(AL.getLoc(), diag::warn_attribute_underlying_type_mismatch) << EnumUnderlyingType << type << BitfieldType;
+      return;
+    }
+  }
+
+  D->addAttr(::new (S.Context) DebugInfoTypeAttr(S.Context, AL, ParmTSI));
+}
+
 //===----------------------------------------------------------------------===//
 // Checker-specific attribute handlers.
 //===----------------------------------------------------------------------===//
@@ -9629,6 +9651,10 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
     handleBuiltinAliasAttr(S, D, AL);
     break;
 
+  case ParsedAttr::AT_DebugInfoType:
+    handleDebugInfoTypeAttr(S, D, AL);
+    break;
+
   case ParsedAttr::AT_UsingIfExists:
     handleSimpleAttribute<UsingIfExistsAttr>(S, D, AL);
     break;
diff --git a/clang/test/CodeGen/debug-info-debug-info-type.cpp b/clang/test/CodeGen/debug-info-debug-info-type.cpp
new file mode 100644
index 000000000000000..4b60d1b64be239f
--- /dev/null
+++ b/clang/test/CodeGen/debug-info-debug-info-type.cpp
@@ -0,0 +1,14 @@
+// RUN: %clang -target x86_64-linux -g -S -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -verify -DMISMATCH %s
+
+struct A {
+  enum E : unsigned {};
+  [[clang::debug_info_type(E)]] unsigned b : 2;
+#ifdef MISMATCH
+  [[clang::debug_info_type(E)]] int b2 : 2;
+  // expected-warning@-1 {{underlying type 'unsigned int' of enumeration 'E' doesn't match bitfield type 'int'}}
+#endif
+} 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]]

@github-actions
Copy link

github-actions bot commented Oct 15, 2023

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

@dwblaikie
Copy link
Collaborator

This is useful when user is forced to use the same type for all bitfields in a class to get better layout and codegen from MSVC

Does this issue not apply to other platforms? (I didn't think you could bitfield enums)

I'd think maybe rather than adding an attribute to workaround MSVC's missing support - code could #ifdef in a way that's ABI compatible, maybe? (though I admit that's a subtle thing to get right & the cost for getting it wrong is significant)

@Endilll
Copy link
Contributor Author

Endilll commented Oct 15, 2023

Does this issue not apply to other platforms?

As far as I understand, MS ABI forces compilers to insert padding between bitfields more often than Itanium. Probably because of that GCC and Clang are able to merge separate loads of bitfields into single load on Linux.

I'd think maybe rather than adding an attribute to workaround MSVC's missing support - code could #ifdef in a way that's ABI compatible, maybe? (though I admit that's a subtle thing to get right & the cost for getting it wrong is significant)

It's technically possible to #ifdef this for Linux, but it's not going to work on Windows, because we have to show the compiler that bitfields have the same type (for Clang it's usually unsigned). Otherwise we get bad layouts and bad codegen, which is an unacceptable regression.

Even if we focus on Linux, I'd rather pay with lots of boilerplate in LLDB formatter (lower estimate is 4 lines per every single bitfield), than litter Clang headers with #ifdefs for the sake of debuggers.

@pogo59
Copy link
Collaborator

pogo59 commented Oct 15, 2023

Does this issue not apply to other platforms?

There is a deliberate layout/ABI choice that MSVC made ages ago and will never change. It will only pack bitfields into the same word if the neighboring bitfield declarations have the same base type. This is relatively well known.

That said, being able to attach an enum (or whatever) to the bitfield does seem handy, for any target.

@dwblaikie
Copy link
Collaborator

Seems a bit unfortunate/bit of a heavyweight workaround - but I don't fundamentally object if @AaronBallman or co are OK with the new attribute for this.

@philnik777
Copy link
Contributor

Wouldn't it be better to go the other way around? i.e. have a [[clang::compressed_bitfield]] (or whatever) which influences the ABI, so it's possible to do stuff like

[[clang::compressed_bitfield]] bool IsSomething : 1;
[[clang::compressed_bitfield]] MyEnum Whatever : 3;
[[clang::compressed_bitfield]] int MoreStuff : 4;

which the current approach doesn't allow.

@dwblaikie
Copy link
Collaborator

Wouldn't it be better to go the other way around? i.e. have a [[clang::compressed_bitfield]] (or whatever) which influences the ABI, so it's possible to do stuff like

[[clang::compressed_bitfield]] bool IsSomething : 1;
[[clang::compressed_bitfield]] MyEnum Whatever : 3;
[[clang::compressed_bitfield]] int MoreStuff : 4;

which the current approach doesn't allow.

The issue is that MSVC wouldn't know/recognize/implement this attribute - so clang couldn't do that without breaking ABI compatibility with MSVC.

@philnik777
Copy link
Contributor

Wouldn't it be better to go the other way around? i.e. have a [[clang::compressed_bitfield]] (or whatever) which influences the ABI, so it's possible to do stuff like

[[clang::compressed_bitfield]] bool IsSomething : 1;
[[clang::compressed_bitfield]] MyEnum Whatever : 3;
[[clang::compressed_bitfield]] int MoreStuff : 4;

which the current approach doesn't allow.

The issue is that MSVC wouldn't know/recognize/implement this attribute - so clang couldn't do that without breaking ABI compatibility with MSVC.

That's not important for everybody, and you could still #if around that code like

#if __has_cpp_attribute(clang::compressed_bitfield)
#define BITFILED_T(ActualType, ABIType) static_assert(check_abi_compat<ActualType, ABIType>()); [[clang::compressed_bitfield]] ActualType
#else
#define BITFIELD_T(ActualType, ABIType) ABIType
...

Sure, it's not super pretty, but it gets you there and allows you to do more stuff. We could also ask the MSVC folks whether there would be interest to add such an attribute, since there is clearly interest in having a good ABI and stronger typing (CC @StephanTLavavej @CaseyCarter).

@AaronBallman
Copy link
Collaborator

Wouldn't it be better to go the other way around? i.e. have a [[clang::compressed_bitfield]] (or whatever) which influences the ABI, so it's possible to do stuff like

[[clang::compressed_bitfield]] bool IsSomething : 1;
[[clang::compressed_bitfield]] MyEnum Whatever : 3;
[[clang::compressed_bitfield]] int MoreStuff : 4;

which the current approach doesn't allow.

The issue is that MSVC wouldn't know/recognize/implement this attribute - so clang couldn't do that without breaking ABI compatibility with MSVC.

That's not important for everybody, and you could still #if around that code like

#if __has_cpp_attribute(clang::compressed_bitfield)
#define BITFILED_T(ActualType, ABIType) static_assert(check_abi_compat<ActualType, ABIType>()); [[clang::compressed_bitfield]] ActualType
#else
#define BITFIELD_T(ActualType, ABIType) ABIType
...

Sure, it's not super pretty, but it gets you there and allows you to do more stuff. We could also ask the MSVC folks whether there would be interest to add such an attribute, since there is clearly interest in having a good ABI and stronger typing (CC @StephanTLavavej @CaseyCarter).

I think that's a somewhat orthogonal feature to the one @Endilll is proposing. While it might make sense to ask the MSVC and GCC folks if they'd be willing to entertain a similar attribute, the implementation landscape is much wider in C and those implementations may have similar ABI issues. If an implementation ignores the compressed_bitfield attribute, you get potential ABI issues; if an implementation ignores the debug_info_type attribute, you get the default debugging experience but no change to codegen behavior. So I think there may be room for both features.

We have a preferred_name attribute already for specifying "please display this name instead of that name" in diagnostics; this seems somewhat similar in nature in that it's "please display this type instead of that bit-field type" in the debugger, so mayyybe we want to name this preferred_type instead? If this could also be useful for diagnostics which mention types and can be triggered with a bit-field, that might also be a reason to use a different name (I'm not certain we have many/any such diagnostics today though.) I'm not opposed to the current name, but more thinking that it might be too specific and we may want a path to more generalization in the future.

@erichkeane
Copy link
Collaborator

Wouldn't it be better to go the other way around? i.e. have a [[clang::compressed_bitfield]] (or whatever) which influences the ABI, so it's possible to do stuff like

[[clang::compressed_bitfield]] bool IsSomething : 1;
[[clang::compressed_bitfield]] MyEnum Whatever : 3;
[[clang::compressed_bitfield]] int MoreStuff : 4;

which the current approach doesn't allow.

The issue is that MSVC wouldn't know/recognize/implement this attribute - so clang couldn't do that without breaking ABI compatibility with MSVC.

That's not important for everybody, and you could still #if around that code like

#if __has_cpp_attribute(clang::compressed_bitfield)
#define BITFILED_T(ActualType, ABIType) static_assert(check_abi_compat<ActualType, ABIType>()); [[clang::compressed_bitfield]] ActualType
#else
#define BITFIELD_T(ActualType, ABIType) ABIType
...

Sure, it's not super pretty, but it gets you there and allows you to do more stuff. We could also ask the MSVC folks whether there would be interest to add such an attribute, since there is clearly interest in having a good ABI and stronger typing (CC @StephanTLavavej @CaseyCarter).

I think that's a somewhat orthogonal feature to the one @Endilll is proposing. While it might make sense to ask the MSVC and GCC folks if they'd be willing to entertain a similar attribute, the implementation landscape is much wider in C and those implementations may have similar ABI issues. If an implementation ignores the compressed_bitfield attribute, you get potential ABI issues; if an implementation ignores the debug_info_type attribute, you get the default debugging experience but no change to codegen behavior. So I think there may be room for both features.

We have a preferred_name attribute already for specifying "please display this name instead of that name" in diagnostics; this seems somewhat similar in nature in that it's "please display this type instead of that bit-field type" in the debugger, so mayyybe we want to name this preferred_type instead? If this could also be useful for diagnostics which mention types and can be triggered with a bit-field, that might also be a reason to use a different name (I'm not certain we have many/any such diagnostics today though.) I'm not opposed to the current name, but more thinking that it might be too specific and we may want a path to more generalization in the future.

While I agree that a 'pack-at-the-bit-level' type attribute might be valuable, I too see that as a 'different' problem. There is plenty of code (even in Clang itself) where and Enum is being stored as a bitfield, and more interesting debugging information could be generated for it. I don't mind the name, I think there IS a nice level of symmetry to preferred_type, which I think ALSO has the advantage of being useful (by name!) for non-debug diagnostics.

One thing I REALLY hate about our pattern of using bitfields for enums, is that it becomes pretty trivial to add an entry to an enum and forget to increase the size of the bitfield. I think such a attribute could cause a diagnostic of bitfield says it holds an ENUM type, but can't store all the values, which would be INCREDIBLY valuable (not that I'm pushing that this patch IMPLEMENT that, but more that I'm saying such a name COULD do that, and probably should as well).

So in conclusion, I don't mind the name, but also prefer something more general.

@philnik777
Copy link
Contributor

One thing I REALLY hate about our pattern of using bitfields for enums, is that it becomes pretty trivial to add an entry to an enum and forget to increase the size of the bitfield. I think such a attribute could cause a diagnostic of bitfield says it holds an ENUM type, but can't store all the values, which would be INCREDIBLY valuable (not that I'm pushing that this patch IMPLEMENT that, but more that I'm saying such a name COULD do that, and probably should as well).

So in conclusion, I don't mind the name, but also prefer something more general.

Couldn't we add a warning for that in general? Seems like a pretty useful one to me.

@erichkeane
Copy link
Collaborator

One thing I REALLY hate about our pattern of using bitfields for enums, is that it becomes pretty trivial to add an entry to an enum and forget to increase the size of the bitfield. I think such a attribute could cause a diagnostic of bitfield says it holds an ENUM type, but can't store all the values, which would be INCREDIBLY valuable (not that I'm pushing that this patch IMPLEMENT that, but more that I'm saying such a name COULD do that, and probably should as well).
So in conclusion, I don't mind the name, but also prefer something more general.

Couldn't we add a warning for that in general? Seems like a pretty useful one to me.

Not without SOME sort of psychic-level analysis from Sema. Without some level of mapping of "I mean for this to hold the values of this Enum", we don't have the information to do so. We cannot take advantage of assignments (though a similar warning DOES exist in the 'range' checking) since it might be intended that the field does some level of 'conversion' between enums.

@philnik777
Copy link
Contributor

One thing I REALLY hate about our pattern of using bitfields for enums, is that it becomes pretty trivial to add an entry to an enum and forget to increase the size of the bitfield. I think such a attribute could cause a diagnostic of bitfield says it holds an ENUM type, but can't store all the values, which would be INCREDIBLY valuable (not that I'm pushing that this patch IMPLEMENT that, but more that I'm saying such a name COULD do that, and probably should as well).
So in conclusion, I don't mind the name, but also prefer something more general.

Couldn't we add a warning for that in general? Seems like a pretty useful one to me.

Not without SOME sort of psychic-level analysis from Sema. Without some level of mapping of "I mean for this to hold the values of this Enum", we don't have the information to do so. We cannot take advantage of assignments (though a similar warning DOES exist in the 'range' checking) since it might be intended that the field does some level of 'conversion' between enums.

I meant warning on something like https://godbolt.org/z/Ma17xjjc5. That doesn't seem like it should be that hard.

@Endilll
Copy link
Contributor Author

Endilll commented Oct 20, 2023

However, will this actually work in practice in the debugger? If not, perhaps we should limit to just integer and enumeration types for now, leaving the extension for the future.

I composed an example of that:

struct A {
  short a1;
  short a2;
};

struct B {
  [[clang::preferred_type(A)]] unsigned b1 : 32 = 0x000F'000C;
};

int main()
{
    B b;
    return b.b1;
}

Nightly build of LLDB from apt.llvm.org handles it just fine:

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
  }
}

@erichkeane Would you still prefer restricting type argument to integral and enumeration types?

@Endilll Endilll requested a review from adrian-prantl October 20, 2023 16:44
@dwblaikie
Copy link
Collaborator

struct A {
  short a1;
  short a2;
};

struct B {
  [[clang::preferred_type(A)]] unsigned b1 : 32 = 0x000F'000C;
};

int main()
{
    B b;
    return b.b1;
}

An example where the layout doesn't match the normal struct layout might be more interesting? (like the reality of this situation ^ seems indistinguishable from similar code that used the real type and no bitfield, I think? (as far as the DWARF,struct layouts, etc, are concerned)) - maybe a struct with a short and a char (so it should have a byte of tail padding) then putting it in a 24 bit bitfield (maybe with an 8 bit bitfield after that - to check if attempts to write to the first bitfielded struct don't clobber the following bits) - or, maybe even a struct that ends in a bitfield so it's got less-than-whole-byte padding at the end, and see how that overlaps with embedding that as a bitfield in another struct with a subsequent bitfield.

I would guess a DWARF consumer might have more struggles there - though I Don't think it means we shouldn't do it, just expect to have to fix debuggers for this probably-rather-novel DWARF.

@AaronBallman
Copy link
Collaborator

I wonder if we should treat one-bit bit-fields as if they were bool automatically (e.g., create this attribute implicitly in that case). How often do we expect to see one-bit bit-fields that are arithmetic? I'm sure it happens (to multiply against -1, 0, or 1 depending on the sign of the bit-field, for example), but I expect it to be quite rare compared to bool-like use.

I'd be tentative to do that initially, I think I'd prefer that be a future 'enhancement' after we evaluate the fall-out of this. The rest of this patch has some significant promise, and I'd like to not hold it up trying to evaluate that.

I'm retracting my suggestion there. I think this should be a default behavior of the debugger, but it shouldn't be forced on users by the compiler. Users who don't expect that behavior then won't have to fight against implicit compiler behavior.

@erichkeane
Copy link
Collaborator

However, will this actually work in practice in the debugger? If not, perhaps we should limit to just integer and enumeration types for now, leaving the extension for the future.

I composed an example of that:

struct A {
  short a1;
  short a2;
};

struct B {
  [[clang::preferred_type(A)]] unsigned b1 : 32 = 0x000F'000C;
};

int main()
{
    B b;
    return b.b1;
}

Nightly build of LLDB from apt.llvm.org handles it just fine:

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
  }
}

@erichkeane Would you still prefer restricting type argument to integral and enumeration types?

I think that is the least dangerous, but the requireCompleteType isn't awful (and I can do a disagree & commit on). I think it gets goofy when the sizes don't match with floating point/complex/etc, but I also see "you got what you asked for!" as being a reasonable defense to that.

@Endilll
Copy link
Contributor Author

Endilll commented Oct 20, 2023

but I also see "you got what you asked for!" as being a reasonable defense to that.

That's my thinking indeed, and the reason why I opposed to Aaron's proposal to implicitly mark 1-bit bit-fields as preferred_type(bool).

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.

The changes should also come with a release note, but I'm generally happy with the code. I did have some suggestions for changes to documentation though.

@AaronBallman
Copy link
Collaborator

The changes should also come with a release note, but I'm generally happy with the code. I did have some suggestions for changes to documentation though.

Suggestion for the release note would be something along these lines:

- 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``.

@Endilll
Copy link
Contributor Author

Endilll commented Oct 23, 2023

Thank you @AaronBallman for writing even more documentation!

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.

These changes LGTM, but please give @erichkeane a chance to chime in given his previous feedback.

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

LGTM, i think we're in an acceptable way forward, particularly on diagnostics.

@Endilll Endilll merged commit 70982ef into llvm:main Oct 23, 2023
@Endilll Endilll deleted the debug-info-type-attr branch October 23, 2023 18:22
Endilll added a commit that referenced this pull request Nov 2, 2023
…70632)

#69104 introduce a diagnostic that checked underlying type of an enum against type of bit-field that is annotated with `[[clang::preferred_type]]`. When I tried to introduce this annotation in #70349, it turned out to be too chatty, despite effort to avoid that.
Endilll added a commit that referenced this pull request Nov 2, 2023
…70349)

This patch adds `clang::preferred_type` annotations to Type-related bit-fields. At the moment only debug info takes advantage of this annotation. See more in #69104

This patch also propagates underlying type of several enums from bit-field declaration to enum declaration. I don't see how having them diverge helps.
qihangkong pushed a commit to rvgpu/llvm that referenced this pull request Apr 18, 2024
…(#70349)

This patch adds `clang::preferred_type` annotations to Type-related bit-fields. At the moment only debug info takes advantage of this annotation. See more in llvm/llvm-project#69104

This patch also propagates underlying type of several enums from bit-field declaration to enum declaration. I don't see how having them diverge helps.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category debuginfo extension:clang
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants