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