//===--- 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 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("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, 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 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(); 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(); 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