| //===--- 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> |
| |
| 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; |
| |
| // Packing and alignment requirements for invalid decls are meaningless. |
| if (Struct->isInvalidDecl()) |
| 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. |
| QualType StructFieldTy = StructField->getType(); |
| if (StructFieldTy->isIncompleteType()) |
| return; |
| unsigned int StructFieldWidth = |
| (unsigned int)Result.Context->getTypeInfo(StructFieldTy.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) { |
| FixIt = FixItHint::CreateReplacement( |
| Attribute->getRange(), |
| (Twine("aligned(") + NewAlignQuantity + ")").str()); |
| } else { |
| FixIt = FixItHint::CreateInsertion( |
| Struct->getEndLoc().getLocWithOffset(1), |
| (Twine(" __attribute__((aligned(") + NewAlignQuantity + ")))").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 |