[RFC 09/12] Implement record field randomization algorithms
Connor Kuehl via cfe-commits
cfe-commits at lists.llvm.org
Fri Mar 8 14:36:48 PST 2019
Co-authored-by: Cole Nixon <nixontcole at gmail.com>
Co-authored-by: Connor Kuehl <cipkuehl at gmail.com>
Co-authored-by: James Foster <jafosterja at gmail.com>
Co-authored-by: Jeff Takahashi <jeffrey.takahashi at gmail.com>
Co-authored-by: Jordan Cantrell <jordan.cantrell at mail.com>
Co-authored-by: Nikk Forbus <nicholas.forbus at gmail.com>
Co-authored-by: Tim Pugh <nwtpugh at 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
More information about the cfe-commits
mailing list