forked from OSchip/llvm-project
145 lines
5.5 KiB
C++
145 lines
5.5 KiB
C++
//===--- StructPackAlignCheck.cpp - clang-tidy ----------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "StructPackAlignCheck.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/RecordLayout.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include <math.h>
|
|
#include <sstream>
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang {
|
|
namespace tidy {
|
|
namespace altera {
|
|
|
|
void StructPackAlignCheck::registerMatchers(MatchFinder *Finder) {
|
|
Finder->addMatcher(recordDecl(isStruct(), isDefinition(),
|
|
unless(isExpansionInSystemHeader()))
|
|
.bind("struct"),
|
|
this);
|
|
}
|
|
|
|
CharUnits
|
|
StructPackAlignCheck::computeRecommendedAlignment(CharUnits MinByteSize) {
|
|
CharUnits NewAlign = CharUnits::fromQuantity(1);
|
|
if (!MinByteSize.isPowerOfTwo()) {
|
|
int MSB = (int)MinByteSize.getQuantity();
|
|
for (; MSB > 0; MSB /= 2) {
|
|
NewAlign = NewAlign.alignTo(
|
|
CharUnits::fromQuantity(((int)NewAlign.getQuantity()) * 2));
|
|
// Abort if the computed alignment meets the maximum configured alignment.
|
|
if (NewAlign.getQuantity() >= MaxConfiguredAlignment)
|
|
break;
|
|
}
|
|
} else {
|
|
NewAlign = MinByteSize;
|
|
}
|
|
return NewAlign;
|
|
}
|
|
|
|
void StructPackAlignCheck::check(const MatchFinder::MatchResult &Result) {
|
|
const auto *Struct = Result.Nodes.getNodeAs<RecordDecl>("struct");
|
|
|
|
// Do not trigger on templated struct declarations because the packing and
|
|
// alignment requirements are unknown.
|
|
if (Struct->isTemplated())
|
|
return;
|
|
|
|
// Get sizing info for the struct.
|
|
llvm::SmallVector<std::pair<unsigned int, unsigned int>, 10> FieldSizes;
|
|
unsigned int TotalBitSize = 0;
|
|
for (const FieldDecl *StructField : Struct->fields()) {
|
|
// For each StructField, record how big it is (in bits).
|
|
// Would be good to use a pair of <offset, size> to advise a better
|
|
// packing order.
|
|
unsigned int StructFieldWidth =
|
|
(unsigned int)Result.Context
|
|
->getTypeInfo(StructField->getType().getTypePtr())
|
|
.Width;
|
|
FieldSizes.emplace_back(StructFieldWidth, StructField->getFieldIndex());
|
|
// FIXME: Recommend a reorganization of the struct (sort by StructField
|
|
// size, largest to smallest).
|
|
TotalBitSize += StructFieldWidth;
|
|
}
|
|
|
|
uint64_t CharSize = Result.Context->getCharWidth();
|
|
CharUnits CurrSize = Result.Context->getASTRecordLayout(Struct).getSize();
|
|
CharUnits MinByteSize =
|
|
CharUnits::fromQuantity(ceil((float)TotalBitSize / CharSize));
|
|
CharUnits MaxAlign = CharUnits::fromQuantity(
|
|
ceil((float)Struct->getMaxAlignment() / CharSize));
|
|
CharUnits CurrAlign =
|
|
Result.Context->getASTRecordLayout(Struct).getAlignment();
|
|
CharUnits NewAlign = computeRecommendedAlignment(MinByteSize);
|
|
|
|
bool IsPacked = Struct->hasAttr<PackedAttr>();
|
|
bool NeedsPacking = (MinByteSize < CurrSize) && (MaxAlign != NewAlign) &&
|
|
(CurrSize != NewAlign);
|
|
bool NeedsAlignment = CurrAlign.getQuantity() != NewAlign.getQuantity();
|
|
|
|
if (!NeedsAlignment && !NeedsPacking)
|
|
return;
|
|
|
|
// If it's using much more space than it needs, suggest packing.
|
|
// (Do not suggest packing if it is currently explicitly aligned to what the
|
|
// minimum byte size would suggest as the new alignment.)
|
|
if (NeedsPacking && !IsPacked) {
|
|
diag(Struct->getLocation(),
|
|
"accessing fields in struct %0 is inefficient due to padding; only "
|
|
"needs %1 bytes but is using %2 bytes")
|
|
<< Struct << (int)MinByteSize.getQuantity()
|
|
<< (int)CurrSize.getQuantity()
|
|
<< FixItHint::CreateInsertion(Struct->getEndLoc().getLocWithOffset(1),
|
|
" __attribute__((packed))");
|
|
diag(Struct->getLocation(),
|
|
"use \"__attribute__((packed))\" to reduce the amount of padding "
|
|
"applied to struct %0",
|
|
DiagnosticIDs::Note)
|
|
<< Struct;
|
|
}
|
|
|
|
FixItHint FixIt;
|
|
AlignedAttr *Attribute = Struct->getAttr<AlignedAttr>();
|
|
std::string NewAlignQuantity = std::to_string((int)NewAlign.getQuantity());
|
|
if (Attribute) {
|
|
std::ostringstream FixItString;
|
|
FixItString << "aligned(" << NewAlignQuantity << ")";
|
|
FixIt =
|
|
FixItHint::CreateReplacement(Attribute->getRange(), FixItString.str());
|
|
} else {
|
|
std::ostringstream FixItString;
|
|
FixItString << " __attribute__((aligned(" << NewAlignQuantity << ")))";
|
|
FixIt = FixItHint::CreateInsertion(Struct->getEndLoc().getLocWithOffset(1),
|
|
FixItString.str());
|
|
}
|
|
|
|
// And suggest the minimum power-of-two alignment for the struct as a whole
|
|
// (with and without packing).
|
|
if (NeedsAlignment) {
|
|
diag(Struct->getLocation(),
|
|
"accessing fields in struct %0 is inefficient due to poor alignment; "
|
|
"currently aligned to %1 bytes, but recommended alignment is %2 bytes")
|
|
<< Struct << (int)CurrAlign.getQuantity() << NewAlignQuantity << FixIt;
|
|
|
|
diag(Struct->getLocation(),
|
|
"use \"__attribute__((aligned(%0)))\" to align struct %1 to %0 bytes",
|
|
DiagnosticIDs::Note)
|
|
<< NewAlignQuantity << Struct;
|
|
}
|
|
}
|
|
|
|
void StructPackAlignCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
|
Options.store(Opts, "MaxConfiguredAlignment", MaxConfiguredAlignment);
|
|
}
|
|
|
|
} // namespace altera
|
|
} // namespace tidy
|
|
} // namespace clang
|