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