Co-authored-by: Cole Nixon <nixontc...@gmail.com> Co-authored-by: Connor Kuehl <cipku...@gmail.com> Co-authored-by: James Foster <jafoste...@gmail.com> Co-authored-by: Jeff Takahashi <jeffrey.takaha...@gmail.com> Co-authored-by: Jordan Cantrell <jordan.cantr...@mail.com> Co-authored-by: Nikk Forbus <nicholas.for...@gmail.com> Co-authored-by: Tim Pugh <nwtp...@gmail.com> --- clang/include/clang/AST/DeclBase.h | 2 + .../clang/AST/RecordFieldReorganizer.h | 59 +++++ clang/lib/AST/RecordFieldReorganizer.cpp | 238 ++++++++++++++++++ 3 files changed, 299 insertions(+) create mode 100644 clang/include/clang/AST/RecordFieldReorganizer.h
diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h index 4b63e540d5d..b08acadc9ff 100644 --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -1270,6 +1270,8 @@ class DeclContext { friend class ExternalASTSource; /// For CreateStoredDeclsMap friend class DependentDiagnostic; + /// For fine-grained control of field order + friend class RecordFieldReorganizer; /// For hasNeedToReconcileExternalVisibleStorage, /// hasLazyLocalLexicalLookups, hasLazyExternalLexicalLookups friend class ASTWriter; diff --git a/clang/include/clang/AST/RecordFieldReorganizer.h b/clang/include/clang/AST/RecordFieldReorganizer.h new file mode 100644 index 00000000000..fa29014348f --- /dev/null +++ b/clang/include/clang/AST/RecordFieldReorganizer.h @@ -0,0 +1,59 @@ +//===-- RecordFieldReorganizer.h - Interface for manipulating field order --*- +// C++ +//-*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This header file contains the base class that defines an interface for +// manipulating a RecordDecl's field layouts. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_AST_RECORDFIELDREORGANIZER_H +#define LLVM_CLANG_LIB_AST_RECORDFIELDREORGANIZER_H + +#include "Decl.h" +#include <random> + +namespace clang { + +// FIXME: Find a better alternative to SmallVector with hardcoded size! + +class RecordFieldReorganizer { +public: + virtual ~RecordFieldReorganizer() = default; + void reorganizeFields(const ASTContext &C, const RecordDecl *D); + +protected: + virtual void reorganize(const ASTContext &C, const RecordDecl *D, + SmallVector<Decl *, 64> &NewOrder) = 0; + +private: + void commit(const RecordDecl *D, + SmallVectorImpl<Decl *> &NewFieldOrder) const; +}; + +class Randstruct : public RecordFieldReorganizer { +public: + Randstruct(std::string seed) : Seq(seed.begin(), seed.end()), rng(Seq) {} + + /// Determines if the Record can be safely and easily randomized based on certain criteria (see implementation). + static bool isTriviallyRandomizable(const RecordDecl *D); +protected: + SmallVector<Decl *, 64> randomize(SmallVector<Decl *, 64> fields); + SmallVector<Decl *, 64> perfrandomize(const ASTContext &ctx, + SmallVector<Decl *, 64> fields); + virtual void reorganize(const ASTContext &C, const RecordDecl *D, + SmallVector<Decl *, 64> &NewOrder) override; +private: + std::seed_seq Seq; + std::default_random_engine rng; +}; + +} // namespace clang + +#endif diff --git a/clang/lib/AST/RecordFieldReorganizer.cpp b/clang/lib/AST/RecordFieldReorganizer.cpp index c7ab9cd16d4..a37490d5784 100644 --- a/clang/lib/AST/RecordFieldReorganizer.cpp +++ b/clang/lib/AST/RecordFieldReorganizer.cpp @@ -11,9 +11,247 @@ // //===----------------------------------------------------------------------===// +#include "clang/AST/RecordFieldReorganizer.h" +#include "clang/AST/ASTContext.h" #include "clang/AST/RandstructSeed.h" +#include <algorithm> +#include <cstdint> +#include <random> +#include <set> +#include <vector> + +// FIXME: Find a better alternative to SmallVector with hardcoded size! + namespace clang { std::string RandstructSeed = ""; bool RandstructAutoSelect = false; + +void RecordFieldReorganizer::reorganizeFields(const ASTContext &C, + const RecordDecl *D) { + // Save original fields for asserting later that a subclass hasn't + // sabotaged the RecordDecl by removing or adding fields + std::set<Decl *> mutateGuard; + + SmallVector<Decl *, 64> fields; + for (auto f : D->fields()) { + mutateGuard.insert(f); + fields.push_back(f); + } + // Now allow subclass implementations to reorder the fields + reorganize(C, D, fields); + + // Assert all fields are still present + assert(mutateGuard.size() == fields.size() && + "Field count altered after reorganization"); + for (auto f : fields) { + auto found = std::find(std::begin(mutateGuard), std::end(mutateGuard), f); + assert(found != std::end(mutateGuard) && + "Unknown field encountered after reorganization"); + } + + commit(D, fields); +} +void RecordFieldReorganizer::commit( + const RecordDecl *D, SmallVectorImpl<Decl *> &NewFieldOrder) const { + Decl *First, *Last; + std::tie(First, Last) = DeclContext::BuildDeclChain( + NewFieldOrder, D->hasLoadedFieldsFromExternalStorage()); + D->FirstDecl = First; + D->LastDecl = Last; +} + +/// Bucket to store fields up to size of a cache line during randomization. +class Bucket { +public: + virtual ~Bucket() = default; + /// Returns a randomized version of the bucket. + virtual SmallVector<FieldDecl *, 64> randomize(std::default_random_engine &rng); + /// Checks if an added element would fit in a cache line. + virtual bool canFit(size_t size) const; + /// Adds a field to the bucket. + void add(FieldDecl *field, size_t size); + /// Is this bucket for bitfields? + virtual bool isBitfieldRun() const; + /// Is this bucket full? + bool full() const; + bool empty() const; + +protected: + size_t size; + SmallVector<FieldDecl *, 64> fields; +}; + +/// BitfieldRun is a bucket for storing adjacent bitfields that may +/// exceed the size of a cache line. +class BitfieldRun : public Bucket { +public: + virtual SmallVector<FieldDecl *, 64> randomize(std::default_random_engine &rng) override; + virtual bool canFit(size_t size) const override; + virtual bool isBitfieldRun() const override; +}; + +// FIXME: Is there a way to detect this? (i.e. on 32bit system vs 64?) +const size_t CACHE_LINE = 64; + +SmallVector<FieldDecl *, 64> Bucket::randomize(std::default_random_engine &rng) { + std::shuffle(std::begin(fields), std::end(fields), rng); + return fields; +} + +bool Bucket::canFit(size_t size) const { + // We will say we can fit any size if the bucket is empty + // because there are many instances where a field is much + // larger than 64 bits (i.e., an array, a structure, etc) + // but it still must be placed into a bucket. + // + // Otherwise, if the bucket has elements and we're still + // trying to create a cache-line sized grouping, we cannot + // fit a larger field in here. + return empty() || this->size + size <= CACHE_LINE; +} + +void Bucket::add(FieldDecl *field, size_t size) { + fields.push_back(field); + this->size += size; +} + +bool Bucket::isBitfieldRun() const { + // The normal bucket is not a bitfieldrun. This is to avoid RTTI. + return false; +} + +bool Bucket::full() const { + // We're full if our size is a cache line. + return size >= CACHE_LINE; +} + +bool Bucket::empty() const { return size == 0; } + +SmallVector<FieldDecl *, 64> BitfieldRun::randomize(std::default_random_engine &rng) { + // Keep bit fields adjacent, we will not scramble them. + return fields; +} + +bool BitfieldRun::canFit(size_t size) const { + // We can always fit another adjacent bitfield. + return true; +} + +bool BitfieldRun::isBitfieldRun() const { return true; } + +SmallVector<Decl *, 64> Randstruct::randomize(SmallVector<Decl *, 64> fields) { + std::shuffle(std::begin(fields), std::end(fields), rng); + return fields; +} + +SmallVector<Decl *, 64> Randstruct::perfrandomize(const ASTContext &ctx, + SmallVector<Decl *, 64> fields) { + // All of the buckets produced by best-effort cache-line algorithm. + std::vector<std::unique_ptr<Bucket>> buckets; + + // The current bucket of fields that we are trying to fill to a cache-line. + std::unique_ptr<Bucket> currentBucket = nullptr; + // The current bucket containing the run of adjacent bitfields to ensure + // they remain adjacent. + std::unique_ptr<Bucket> currentBitfieldRun = nullptr; + + // Tracks the number of fields that we failed to fit to the current bucket, + // and thus still need to be added later. + size_t skipped = 0; + + while (!fields.empty()) { + // If we've skipped more fields than we have remaining to place, + // that means that they can't fit in our current bucket, and we + // need to start a new one. + if (skipped >= fields.size()) { + skipped = 0; + buckets.push_back(std::move(currentBucket)); + } + + // Take the first field that needs to be put in a bucket. + auto field = fields.begin(); + auto *f = llvm::cast<FieldDecl>(*field); + + if (f->isBitField()) { + // Start a bitfield run if this is the first bitfield + // we have found. + if (!currentBitfieldRun) { + currentBitfieldRun = llvm::make_unique<BitfieldRun>(); + } + + // We've placed the field, and can remove it from the + // "awaiting buckets" vector called "fields" + currentBitfieldRun->add(f, 1); + fields.erase(field); + } else { + // Else, current field is not a bitfield + // If we were previously in a bitfield run, end it. + if (currentBitfieldRun) { + buckets.push_back(std::move(currentBitfieldRun)); + } + // If we don't have a bucket, make one. + if (!currentBucket) { + currentBucket = llvm::make_unique<Bucket>(); + } + + auto width = ctx.getTypeInfo(f->getType()).Width; + + // If we can fit, add it. + if (currentBucket->canFit(width)) { + currentBucket->add(f, width); + fields.erase(field); + + // If it's now full, tie off the bucket. + if (currentBucket->full()) { + skipped = 0; + buckets.push_back(std::move(currentBucket)); + } + } else { + // We can't fit it in our current bucket. + // Move to the end for processing later. + ++skipped; // Mark it skipped. + fields.push_back(f); + fields.erase(field); + } + } + } + + // Done processing the fields awaiting a bucket. + + // If we were filling a bucket, tie it off. + if (currentBucket) { + buckets.push_back(std::move(currentBucket)); + } + + // If we were processing a bitfield run bucket, tie it off. + if (currentBitfieldRun) { + buckets.push_back(std::move(currentBitfieldRun)); + } + + std::shuffle(std::begin(buckets), std::end(buckets), rng); + + // Produce the new ordering of the elements from our buckets. + SmallVector<Decl *, 64> finalOrder; + for (auto &bucket : buckets) { + auto randomized = bucket->randomize(rng); + finalOrder.insert(finalOrder.end(), randomized.begin(), randomized.end()); + } + + return finalOrder; +} + +void Randstruct::reorganize(const ASTContext &C, const RecordDecl *D, + SmallVector<Decl *, 64> &NewOrder) { + SmallVector<Decl *, 64> randomized = perfrandomize(C, NewOrder); + NewOrder = randomized; +} +bool Randstruct::isTriviallyRandomizable(const RecordDecl *D) { + for (auto f : D->fields()){ + //If an element of the structure does not have a + //function type is not a function pointer + if(f->getFunctionType() == nullptr){ return false; } + } + return true; +} } // namespace clang -- 2.17.1 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits