Skip to content

[clangd] Show alignment for records and fields decls #67213

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 1 commit into from
Oct 22, 2023
Merged

[clangd] Show alignment for records and fields decls #67213

merged 1 commit into from
Oct 22, 2023

Conversation

sr-tream
Copy link
Contributor

@sr-tream sr-tream commented Sep 23, 2023

Shows align for records and fields declarations in hover information.

Example:

struct A {
  char a;
  short b;
};

For this struct hover informations shows:

Size: 4 bytes, alignment 2 bytes

image

@llvmbot llvmbot added the clangd label Sep 23, 2023
@llvmbot
Copy link
Member

llvmbot commented Sep 23, 2023

@llvm/pr-subscribers-clangd

Changes

Shows align for records and fields declarations in hover information.

Example:

struct A {
  char a;
  short b;
};

For this struct hover informations shows:

Size: 4 bytes
Align: 2 bytes

image


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

3 Files Affected:

  • (modified) clang-tools-extra/clangd/Hover.cpp (+5)
  • (modified) clang-tools-extra/clangd/Hover.h (+2)
  • (modified) clang-tools-extra/clangd/unittests/HoverTests.cpp (+11)
diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp
index 0ec85fc24df151b..f2cc6637069532b 100644
--- a/clang-tools-extra/clangd/Hover.cpp
+++ b/clang-tools-extra/clangd/Hover.cpp
@@ -1001,6 +1001,8 @@ void addLayoutInfo(const NamedDecl &ND, HoverInfo &HI) {
   if (auto *RD = llvm::dyn_cast<RecordDecl>(&ND)) {
     if (auto Size = Ctx.getTypeSizeInCharsIfKnown(RD->getTypeForDecl()))
       HI.Size = Size->getQuantity() * 8;
+    if (!RD->isInvalidDecl() && !RD->isDependentType())
+      HI.Align = Ctx.getTypeAlign(RD->getTypeForDecl());
     return;
   }
 
@@ -1009,6 +1011,7 @@ void addLayoutInfo(const NamedDecl &ND, HoverInfo &HI) {
     if (Record)
       Record = Record->getDefinition();
     if (Record && !Record->isInvalidDecl() && !Record->isDependentType()) {
+      HI.Align = Ctx.getTypeAlign(FD->getType());
       const ASTRecordLayout &Layout = Ctx.getASTRecordLayout(Record);
       HI.Offset = Layout.getFieldOffset(FD->getFieldIndex());
       if (FD->isBitField())
@@ -1488,6 +1491,8 @@ markup::Document HoverInfo::present() const {
           llvm::formatv(" (+{0} padding)", formatSize(*Padding)).str());
     }
   }
+  if (Align)
+    Output.addParagraph().appendText("Align: " + formatSize(*Align));
 
   if (CalleeArgInfo) {
     assert(CallPassType);
diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h
index 6a61100912918ea..fe689de44732ebe 100644
--- a/clang-tools-extra/clangd/Hover.h
+++ b/clang-tools-extra/clangd/Hover.h
@@ -97,6 +97,8 @@ struct HoverInfo {
   std::optional<uint64_t> Offset;
   /// Contains the padding following a field within the enclosing class.
   std::optional<uint64_t> Padding;
+  /// Contains the alignment of fields and types where it's interesting.
+  std::optional<uint64_t> Align;
   // Set when symbol is inside function call. Contains information extracted
   // from the callee definition about the argument this is passed as.
   std::optional<Param> CalleeArgInfo;
diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp
index 8c88cd52574536c..c5623759a7c2041 100644
--- a/clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp
@@ -92,6 +92,7 @@ TEST(Hover, Structured) {
          HI.Offset = 0;
          HI.Size = 8;
          HI.Padding = 56;
+         HI.Align = 8;
          HI.AccessSpecifier = "private";
        }},
       // Union field
@@ -110,6 +111,7 @@ TEST(Hover, Structured) {
          HI.Type = "char";
          HI.Size = 8;
          HI.Padding = 120;
+         HI.Align = 8;
          HI.AccessSpecifier = "public";
        }},
       // Bitfield
@@ -128,6 +130,7 @@ TEST(Hover, Structured) {
          HI.Type = "int";
          HI.Offset = 0;
          HI.Size = 1;
+         HI.Align = 32;
          HI.AccessSpecifier = "public";
        }},
       // Local to class method.
@@ -192,6 +195,7 @@ TEST(Hover, Structured) {
          HI.Type = "char";
          HI.Offset = 0;
          HI.Size = 8;
+         HI.Align = 8;
          HI.AccessSpecifier = "public";
        }},
       // Struct definition shows size.
@@ -204,6 +208,7 @@ TEST(Hover, Structured) {
          HI.Kind = index::SymbolKind::Struct;
          HI.Definition = "struct X {}";
          HI.Size = 8;
+         HI.Align = 8;
        }},
       // Variable with template type
       {R"cpp(
@@ -1375,6 +1380,7 @@ class Foo final {})cpp";
          HI.Offset = 8;
          HI.Size = 1;
          HI.Padding = 23;
+         HI.Align = 8;
          HI.AccessSpecifier = "public";
        }}};
   for (const auto &Case : Cases) {
@@ -1411,6 +1417,7 @@ class Foo final {})cpp";
     EXPECT_EQ(H->Value, Expected.Value);
     EXPECT_EQ(H->Size, Expected.Size);
     EXPECT_EQ(H->Offset, Expected.Offset);
+    EXPECT_EQ(H->Align, Expected.Align);
     EXPECT_EQ(H->AccessSpecifier, Expected.AccessSpecifier);
     EXPECT_EQ(H->CalleeArgInfo, Expected.CalleeArgInfo);
     EXPECT_EQ(H->CallPassType, Expected.CallPassType);
@@ -3448,6 +3455,7 @@ template <typename T, typename C = bool> class Foo {})",
             HI.Size = 32;
             HI.Offset = 96;
             HI.Padding = 32;
+            HI.Align = 32;
           },
           R"(field foo
 
@@ -3455,6 +3463,7 @@ Type: type (aka can_type)
 Value = value
 Offset: 12 bytes
 Size: 4 bytes (+4 bytes padding)
+Align: 4 bytes
 
 // In test::Bar
 def)",
@@ -3470,6 +3479,7 @@ def)",
             HI.Size = 25;
             HI.Offset = 35;
             HI.Padding = 4;
+            HI.Align = 64;
           },
           R"(field foo
 
@@ -3477,6 +3487,7 @@ Type: type (aka can_type)
 Value = value
 Offset: 4 bytes and 3 bits
 Size: 25 bits (+4 bits padding)
+Align: 8 bytes
 
 // In test::Bar
 def)",

Copy link
Contributor

@zyn0217 zyn0217 left a comment

Choose a reason for hiding this comment

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

Thanks! This looks nice, but I think it's better to have more opinions from others.

Moreover, I have a question regarding the test: It seems the alignment for a field depends on the ABI spec, i.e., it could be different across MS ABI and Itanium ABI. Is that number stable for fundamental (or POD/trivial) type on most platforms? (I'm not an expert on ABI, so please correct me if I'm wrong.)

@@ -1001,6 +1001,8 @@ void addLayoutInfo(const NamedDecl &ND, HoverInfo &HI) {
if (auto *RD = llvm::dyn_cast<RecordDecl>(&ND)) {
if (auto Size = Ctx.getTypeSizeInCharsIfKnown(RD->getTypeForDecl()))
HI.Size = Size->getQuantity() * 8;
if (!RD->isInvalidDecl() && !RD->isDependentType())
Copy link
Contributor

Choose a reason for hiding this comment

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

We already have a check for invalid decl at the beginning of the function: if (ND.isInvalidDecl()) return;; Maybe we could avoid this extra check? i.e., if (!RD->isDependentType()) is enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I removed this extra check in force-push

@sr-tream
Copy link
Contributor Author

I'm not an expert on ABI

I too. I use exists function ASTContext::getTypeAlign - it must work correctly, if other functions like ASTContext::getTypeSizeInCharsIfKnown return correct results, because ASTContext::getTypeSizeInCharsIfKnown returns size with paddings for align

@sr-tream
Copy link
Contributor Author

In force-push fixed clangd crash when hovered forward-declared types

Copy link
Contributor

@Endilll Endilll left a comment

Choose a reason for hiding this comment

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

LGTM, but you should wait for other reviewers before merging.

@@ -1488,6 +1491,8 @@ markup::Document HoverInfo::present() const {
llvm::formatv(" (+{0} padding)", formatSize(*Padding)).str());
}
}
if (Align)
Output.addParagraph().appendText("Align: " + formatSize(*Align));
Copy link
Contributor

Choose a reason for hiding this comment

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

My preference would be to have alignment on the same line as size, so that less vertical space is wasted. The following hover should illustrate why this might be more important than it appears on other hovers posted in this PR:
image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My preference would be to have alignment on the same line as size

But size may include padding for member fields. How would you like to see it?

Like this?

Size 24 bytes (8 bytes align) // for structs/classes
Size 1 byte (+7 bytes padding, 8 bytes align) // for member fields with padding

Copy link
Contributor

Choose a reason for hiding this comment

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

That would work, but I'd prefer Size 24 bytes, alignment 8 bytes wording. I don't think alignment as a property of type is secondary to size to put it in parentheses.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's looks better

image
image

@Endilll
Copy link
Contributor

Endilll commented Oct 21, 2023

On the topic of ABI, Itanium and Microsoft type layouts are not compatible, even for standard-layout (POD) types. For example, bit-fields are allowed in standard-layout types, but only Microsoft ABI mandates that bit-fields of different types can't be combined, and have to be padded. That's why almost all bit-fields in Clang are unsigned, without regards to types of values they hold. So author did the right thing asking Clang for alignment of types.

Triviality is another aspect of what has been known as POD, and has nothing to do with layouts: http://eel.is/c++draft/class.prop#2

@Endilll Endilll added the enhancement Improving things as opposed to bug fixing, e.g. new or missing feature label Oct 21, 2023
@sr-tream
Copy link
Contributor Author

In force-push changed alignment render, to render it in same line with size

image

@zyn0217
Copy link
Contributor

zyn0217 commented Oct 22, 2023

Thank you @Endilll for the explanation. The remaining part looks good; @sr-tream Do you have access to land the PR? If not, I'm glad to help you. :)

@sr-tream
Copy link
Contributor Author

Do you have access to land the PR

No, help me please to merge this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clangd enhancement Improving things as opposed to bug fixing, e.g. new or missing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants