1 | //===--- StructPackAlignCheck.cpp - clang-tidy ----------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "StructPackAlignCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/AST/RecordLayout.h" |
12 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
13 | #include <cmath> |
14 | |
15 | using namespace clang::ast_matchers; |
16 | |
17 | namespace clang::tidy::altera { |
18 | |
19 | void StructPackAlignCheck::registerMatchers(MatchFinder *Finder) { |
20 | Finder->addMatcher(NodeMatch: recordDecl(isStruct(), isDefinition(), |
21 | unless(isExpansionInSystemHeader())) |
22 | .bind(ID: "struct" ), |
23 | Action: this); |
24 | } |
25 | |
26 | CharUnits |
27 | StructPackAlignCheck::computeRecommendedAlignment(CharUnits MinByteSize) const { |
28 | CharUnits NewAlign = CharUnits::fromQuantity(Quantity: 1); |
29 | if (!MinByteSize.isPowerOfTwo()) { |
30 | CharUnits::QuantityType MSB = MinByteSize.getQuantity(); |
31 | for (; MSB > 0; MSB /= 2) { |
32 | NewAlign = |
33 | NewAlign.alignTo(Align: CharUnits::fromQuantity(Quantity: NewAlign.getQuantity() * 2)); |
34 | // Abort if the computed alignment meets the maximum configured alignment. |
35 | if (NewAlign.getQuantity() >= MaxConfiguredAlignment) |
36 | break; |
37 | } |
38 | } else { |
39 | NewAlign = MinByteSize; |
40 | } |
41 | return NewAlign; |
42 | } |
43 | |
44 | void StructPackAlignCheck::check(const MatchFinder::MatchResult &Result) { |
45 | const auto *Struct = Result.Nodes.getNodeAs<RecordDecl>(ID: "struct" ); |
46 | |
47 | // Do not trigger on templated struct declarations because the packing and |
48 | // alignment requirements are unknown. |
49 | if (Struct->isTemplated()) |
50 | return; |
51 | |
52 | // Packing and alignment requirements for invalid decls are meaningless. |
53 | if (Struct->isInvalidDecl()) |
54 | return; |
55 | |
56 | // Get sizing info for the struct. |
57 | llvm::SmallVector<std::pair<unsigned int, unsigned int>, 10> FieldSizes; |
58 | unsigned int TotalBitSize = 0; |
59 | for (const FieldDecl *StructField : Struct->fields()) { |
60 | // For each StructField, record how big it is (in bits). |
61 | // Would be good to use a pair of <offset, size> to advise a better |
62 | // packing order. |
63 | QualType StructFieldTy = StructField->getType(); |
64 | if (StructFieldTy->isIncompleteType()) |
65 | return; |
66 | unsigned int StructFieldWidth = |
67 | (unsigned int)Result.Context->getTypeInfo(T: StructFieldTy.getTypePtr()) |
68 | .Width; |
69 | FieldSizes.emplace_back(Args&: StructFieldWidth, Args: StructField->getFieldIndex()); |
70 | // FIXME: Recommend a reorganization of the struct (sort by StructField |
71 | // size, largest to smallest). |
72 | TotalBitSize += StructFieldWidth; |
73 | } |
74 | |
75 | uint64_t CharSize = Result.Context->getCharWidth(); |
76 | CharUnits CurrSize = Result.Context->getASTRecordLayout(D: Struct).getSize(); |
77 | CharUnits MinByteSize = |
78 | CharUnits::fromQuantity(Quantity: std::max<clang::CharUnits::QuantityType>( |
79 | a: ceil(x: static_cast<float>(TotalBitSize) / CharSize), b: 1)); |
80 | CharUnits MaxAlign = CharUnits::fromQuantity( |
81 | Quantity: ceil(x: (float)Struct->getMaxAlignment() / CharSize)); |
82 | CharUnits CurrAlign = |
83 | Result.Context->getASTRecordLayout(D: Struct).getAlignment(); |
84 | CharUnits NewAlign = computeRecommendedAlignment(MinByteSize); |
85 | |
86 | bool IsPacked = Struct->hasAttr<PackedAttr>(); |
87 | bool NeedsPacking = (MinByteSize < CurrSize) && (MaxAlign != NewAlign) && |
88 | (CurrSize != NewAlign); |
89 | bool NeedsAlignment = CurrAlign.getQuantity() != NewAlign.getQuantity(); |
90 | |
91 | if (!NeedsAlignment && !NeedsPacking) |
92 | return; |
93 | |
94 | // If it's using much more space than it needs, suggest packing. |
95 | // (Do not suggest packing if it is currently explicitly aligned to what the |
96 | // minimum byte size would suggest as the new alignment.) |
97 | if (NeedsPacking && !IsPacked) { |
98 | diag(Struct->getLocation(), |
99 | "accessing fields in struct %0 is inefficient due to padding; only " |
100 | "needs %1 bytes but is using %2 bytes" ) |
101 | << Struct << (int)MinByteSize.getQuantity() |
102 | << (int)CurrSize.getQuantity() |
103 | << FixItHint::CreateInsertion(InsertionLoc: Struct->getEndLoc().getLocWithOffset(1), |
104 | Code: " __attribute__((packed))" ); |
105 | diag(Struct->getLocation(), |
106 | "use \"__attribute__((packed))\" to reduce the amount of padding " |
107 | "applied to struct %0" , |
108 | DiagnosticIDs::Note) |
109 | << Struct; |
110 | } |
111 | |
112 | FixItHint FixIt; |
113 | auto *Attribute = Struct->getAttr<AlignedAttr>(); |
114 | std::string NewAlignQuantity = std::to_string(val: (int)NewAlign.getQuantity()); |
115 | if (Attribute) { |
116 | FixIt = FixItHint::CreateReplacement( |
117 | Attribute->getRange(), |
118 | (Twine("aligned(" ) + NewAlignQuantity + ")" ).str()); |
119 | } else { |
120 | FixIt = FixItHint::CreateInsertion( |
121 | InsertionLoc: Struct->getEndLoc().getLocWithOffset(1), |
122 | Code: (Twine(" __attribute__((aligned(" ) + NewAlignQuantity + ")))" ).str()); |
123 | } |
124 | |
125 | // And suggest the minimum power-of-two alignment for the struct as a whole |
126 | // (with and without packing). |
127 | if (NeedsAlignment) { |
128 | diag(Struct->getLocation(), |
129 | "accessing fields in struct %0 is inefficient due to poor alignment; " |
130 | "currently aligned to %1 bytes, but recommended alignment is %2 bytes" ) |
131 | << Struct << (int)CurrAlign.getQuantity() << NewAlignQuantity << FixIt; |
132 | |
133 | diag(Struct->getLocation(), |
134 | "use \"__attribute__((aligned(%0)))\" to align struct %1 to %0 bytes" , |
135 | DiagnosticIDs::Note) |
136 | << NewAlignQuantity << Struct; |
137 | } |
138 | } |
139 | |
140 | void StructPackAlignCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
141 | Options.store(Options&: Opts, LocalName: "MaxConfiguredAlignment" , Value: MaxConfiguredAlignment); |
142 | } |
143 | |
144 | } // namespace clang::tidy::altera |
145 | |