blob: 4de2909fd7e7ca2b4d60b31f02defcd49355963c [file] [log] [blame]
//===--- AnyRequest.h - Requests Instances ----------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file defines type-erasing wrappers for requests used by the Evaluator
// class.
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_AST_ANYREQUEST_H
#define SWIFT_AST_ANYREQUEST_H
#include "swift/Basic/SourceLoc.h"
#include "swift/Basic/TypeID.h"
#include "llvm/ADT/DenseMapInfo.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/ADT/PointerIntPair.h"
#include <string>
namespace llvm {
class raw_ostream;
}
namespace swift {
using llvm::hash_code;
using llvm::hash_value;
class DiagnosticEngine;
/// A collection of functions that describe how to perform operations for a
/// specific concrete request, obtained using
/// \c AnyRequestVTable::get<ConcreteRequest>().
struct AnyRequestVTable {
template<typename Request>
struct Impl {
static void copy(const void *input, void *output) {
new (output) Request(*static_cast<const Request *>(input));
}
static hash_code getHash(const void *ptr) {
return hash_value(*static_cast<const Request *>(ptr));
}
static void deleter(void *ptr) {
static_cast<Request *>(ptr)->~Request();
}
static bool isEqual(const void *lhs, const void *rhs) {
return *static_cast<const Request *>(lhs) ==
*static_cast<const Request *>(rhs);
}
static void simpleDisplay(const void *ptr, llvm::raw_ostream &out) {
simple_display(out, *static_cast<const Request *>(ptr));
}
static void diagnoseCycle(const void *ptr, DiagnosticEngine &diags) {
static_cast<const Request *>(ptr)->diagnoseCycle(diags);
}
static void noteCycleStep(const void *ptr, DiagnosticEngine &diags) {
static_cast<const Request *>(ptr)->noteCycleStep(diags);
}
static SourceLoc getNearestLoc(const void *ptr) {
return static_cast<const Request *>(ptr)->getNearestLoc();
}
static bool isCached(const void *ptr) {
return static_cast<const Request *>(ptr)->isCached();
}
};
const uint64_t typeID;
const size_t requestSize;
const std::function<void(const void *, void *)> copy;
const std::function<hash_code(const void *)> getHash;
const std::function<void(void *)> deleter;
const std::function<bool(const void *, const void *)> isEqual;
const std::function<void(const void *, llvm::raw_ostream &)> simpleDisplay;
const std::function<void(const void *, DiagnosticEngine &)> diagnoseCycle;
const std::function<void(const void *, DiagnosticEngine &)> noteCycleStep;
const std::function<SourceLoc(const void *)> getNearestLoc;
const std::function<bool(const void *)> isCached;
bool isDependencySource;
template <typename Request,
typename std::enable_if<Request::isEverCached>::type * = nullptr>
static const AnyRequestVTable *get() {
static const AnyRequestVTable vtable = {
TypeID<Request>::value,
sizeof(Request),
&Impl<Request>::copy,
&Impl<Request>::getHash,
&Impl<Request>::deleter,
&Impl<Request>::isEqual,
&Impl<Request>::simpleDisplay,
&Impl<Request>::diagnoseCycle,
&Impl<Request>::noteCycleStep,
&Impl<Request>::getNearestLoc,
&Impl<Request>::isCached,
Request::isDependencySource,
};
return &vtable;
}
template <typename Request,
typename std::enable_if<!Request::isEverCached>::type * = nullptr>
static const AnyRequestVTable *get() {
static const AnyRequestVTable vtable = {
TypeID<Request>::value,
sizeof(Request),
&Impl<Request>::copy,
&Impl<Request>::getHash,
&Impl<Request>::deleter,
&Impl<Request>::isEqual,
&Impl<Request>::simpleDisplay,
&Impl<Request>::diagnoseCycle,
&Impl<Request>::noteCycleStep,
&Impl<Request>::getNearestLoc,
[](auto){ return false; },
Request::isDependencySource,
};
return &vtable;
}
};
/// Base class for request type-erasing wrappers.
template <typename Derived>
class AnyRequestBase {
friend llvm::DenseMapInfo<Derived>;
protected:
static hash_code hashForHolder(uint64_t typeID, hash_code requestHash) {
return hash_combine(typeID, requestHash);
}
enum class StorageKind : uint8_t {
Normal,
Empty,
Tombstone,
};
/// The vtable and storage kind.
llvm::PointerIntPair<const AnyRequestVTable *, 2, StorageKind> vtableAndKind;
StorageKind getStorageKind() const { return vtableAndKind.getInt(); }
/// Whether this object is storing a value, and is not empty or a tombstone.
bool hasStorage() const {
switch (getStorageKind()) {
case StorageKind::Empty:
case StorageKind::Tombstone:
return false;
case StorageKind::Normal:
return true;
}
llvm_unreachable("Unhandled case in switch");
}
/// Retrieve the vtable to perform operations on the type-erased request.
const AnyRequestVTable *getVTable() const {
assert(hasStorage() && "Shouldn't be querying empty or tombstone");
return vtableAndKind.getPointer();
}
AnyRequestBase(const AnyRequestVTable *vtable, StorageKind storageKind) {
vtableAndKind.setPointer(vtable);
vtableAndKind.setInt(storageKind);
assert((bool)vtable == hasStorage() && "Must have a vtable with storage");
}
AnyRequestBase(const AnyRequestBase &other) {
vtableAndKind = other.vtableAndKind;
}
AnyRequestBase &operator=(const AnyRequestBase &other) {
vtableAndKind = other.vtableAndKind;
return *this;
}
private:
Derived &asDerived() {
return *static_cast<Derived *>(this);
}
const Derived &asDerived() const {
return *static_cast<const Derived *>(this);
}
const void *getRawStorage() const { return asDerived().getRawStorage(); }
public:
/// Cast to a specific (known) type.
template<typename Request>
const Request &castTo() const {
assert(getVTable()->typeID == TypeID<Request>::value &&
"Wrong type in cast");
return *static_cast<const Request *>(getRawStorage());
}
/// Try casting to a specific (known) type, returning \c nullptr on
/// failure.
template<typename Request>
const Request *getAs() const {
if (getVTable()->typeID != TypeID<Request>::value)
return nullptr;
return static_cast<const Request *>(getRawStorage());
}
/// Diagnose a cycle detected for this request.
void diagnoseCycle(DiagnosticEngine &diags) const {
getVTable()->diagnoseCycle(getRawStorage(), diags);
}
/// Note that this request is part of a cycle.
void noteCycleStep(DiagnosticEngine &diags) const {
getVTable()->noteCycleStep(getRawStorage(), diags);
}
/// Retrieve the nearest source location to which this request applies.
SourceLoc getNearestLoc() const {
return getVTable()->getNearestLoc(getRawStorage());
}
bool isCached() const {
return getVTable()->isCached(getRawStorage());
}
bool isDependencySource() const {
return getVTable()->isDependencySource;
}
/// Compare two instances for equality.
friend bool operator==(const AnyRequestBase<Derived> &lhs,
const AnyRequestBase<Derived> &rhs) {
// If the storage kinds don't match, we're done.
if (lhs.getStorageKind() != rhs.getStorageKind())
return false;
// If the storage kinds do match, but there's no storage, they're trivially
// equal.
if (!lhs.hasStorage())
return true;
// Must be storing the same kind of request.
if (lhs.getVTable()->typeID != rhs.getVTable()->typeID)
return false;
return lhs.getVTable()->isEqual(lhs.getRawStorage(), rhs.getRawStorage());
}
friend bool operator!=(const Derived &lhs, const Derived &rhs) {
return !(lhs == rhs);
}
friend hash_code hash_value(const AnyRequestBase<Derived> &req) {
// If there's no storage, return a trivial hash value.
if (!req.hasStorage())
return 1;
auto reqHash = req.getVTable()->getHash(req.getRawStorage());
return hashForHolder(req.getVTable()->typeID, reqHash);
}
friend void simple_display(llvm::raw_ostream &out,
const AnyRequestBase<Derived> &req) {
req.getVTable()->simpleDisplay(req.getRawStorage(), out);
}
};
/// Provides a view onto a request that is stored on the stack. Objects of this
/// class must not outlive the request they reference.
///
/// Requests must be value types and provide the following API:
///
/// - Copy constructor
/// - Equality operator (==)
/// - Hashing support (hash_value)
/// - TypeID support (see swift/Basic/TypeID.h)
/// - Display support (free function):
/// void simple_display(llvm::raw_ostream &, const T &);
/// - Cycle diagnostics operations:
/// void diagnoseCycle(DiagnosticEngine &diags) const;
/// void noteCycleStep(DiagnosticEngine &diags) const;
///
class ActiveRequest final : public AnyRequestBase<ActiveRequest> {
template <typename T>
friend class AnyRequestBase;
friend class AnyRequest;
friend llvm::DenseMapInfo<ActiveRequest>;
/// Pointer to the request stored on the stack.
const void *storage;
/// Creates an \c ActiveRequest without storage.
explicit ActiveRequest(StorageKind storageKind)
: AnyRequestBase(/*vtable*/ nullptr, storageKind) {}
const void *getRawStorage() const { return storage; }
public:
/// Creates a new \c ActiveRequest referencing a concrete request on the
/// stack.
template <typename Request>
explicit ActiveRequest(const Request &request)
: AnyRequestBase(AnyRequestVTable::get<Request>(), StorageKind::Normal) {
storage = &request;
}
};
/// Stores a request (for the \c Evaluator class) of any kind. Unlike
/// \c ActiveRequest, this wrapper has ownership of the underlying request.
///
/// Requests must be value types and provide the following API to be stored in
/// an \c AnyRequest instance:
///
/// - Copy constructor
/// - Equality operator (==)
/// - Hashing support (hash_value)
/// - TypeID support (see swift/Basic/TypeID.h)
/// - Display support (free function):
/// void simple_display(llvm::raw_ostream &, const T &);
/// - Cycle diagnostics operations:
/// void diagnoseCycle(DiagnosticEngine &diags) const;
/// void noteCycleStep(DiagnosticEngine &diags) const;
///
class AnyRequest final : public AnyRequestBase<AnyRequest> {
template <typename T>
friend class AnyRequestBase;
friend llvm::DenseMapInfo<AnyRequest>;
/// Pointer to the request on the heap.
void *storage;
/// Creates an \c AnyRequest without storage.
explicit AnyRequest(StorageKind storageKind)
: AnyRequestBase(/*vtable*/ nullptr, storageKind) {}
const void *getRawStorage() const { return storage; }
/// Whether this wrapper is storing the same underlying request as an
/// \c ActiveRequest.
bool isStorageEqual(const ActiveRequest &other) const {
// If either wrapper isn't storing anything, just return false.
if (!hasStorage() || !other.hasStorage())
return false;
if (getVTable()->typeID != other.getVTable()->typeID)
return false;
return getVTable()->isEqual(getRawStorage(), other.getRawStorage());
}
public:
AnyRequest(const AnyRequest &other) : AnyRequestBase(other) {
if (hasStorage()) {
// For now, just allocate a new buffer and copy across the request. This
// should only happen when performing operations on the (disabled by
// default) dependency graph.
storage = llvm::safe_malloc(getVTable()->requestSize);
getVTable()->copy(other.storage, storage);
}
}
AnyRequest &operator=(const AnyRequest &other) {
if (&other != this) {
this->~AnyRequest();
new (this) AnyRequest(other);
}
return *this;
}
AnyRequest(AnyRequest &&other) : AnyRequestBase(other),
storage(other.storage) {
new (&other) AnyRequest(StorageKind::Empty);
}
AnyRequest &operator=(AnyRequest &&other) {
if (&other != this) {
this->~AnyRequest();
new (this) AnyRequest(std::move(other));
}
return *this;
}
// Create a local template typename `Request` in the template specialization
// so that we can refer to it in the SFINAE condition as well as the body of
// the template itself. The SFINAE condition allows us to remove this
// constructor from candidacy when evaluating explicit construction with an
// instance of `AnyRequest`. If we do not do so, we will find ourselves with
// ambiguity with this constructor and the defined move constructor above.
/// Construct a new instance with the given value.
template <typename T, typename Request = std::decay_t<T>,
typename = typename std::enable_if<
!std::is_same<Request, AnyRequest>::value>::type>
explicit AnyRequest(T &&request)
: AnyRequestBase(AnyRequestVTable::get<Request>(), StorageKind::Normal) {
storage = llvm::safe_malloc(sizeof(Request));
new (storage) Request(std::forward<T>(request));
}
/// Construct an \c AnyRequest from an \c ActiveRequest, allowing the
/// underlying request to persist.
explicit AnyRequest(const ActiveRequest &req)
: AnyRequestBase(req.getVTable(), StorageKind::Normal) {
assert(req.hasStorage());
storage = llvm::safe_malloc(getVTable()->requestSize);
getVTable()->copy(req.storage, storage);
}
~AnyRequest() {
if (hasStorage()) {
getVTable()->deleter(storage);
std::free(storage);
}
}
/// Return the result of calling simple_display as a string.
std::string getAsString() const;
};
} // end namespace swift
namespace llvm {
template<>
struct DenseMapInfo<swift::ActiveRequest> {
using ActiveRequest = swift::ActiveRequest;
static inline ActiveRequest getEmptyKey() {
return ActiveRequest(ActiveRequest::StorageKind::Empty);
}
static inline ActiveRequest getTombstoneKey() {
return ActiveRequest(ActiveRequest::StorageKind::Tombstone);
}
static unsigned getHashValue(const ActiveRequest &request) {
return hash_value(request);
}
static bool isEqual(const ActiveRequest &lhs, const ActiveRequest &rhs) {
return lhs == rhs;
}
};
template<>
struct DenseMapInfo<swift::AnyRequest> {
using AnyRequest = swift::AnyRequest;
using ActiveRequest = swift::ActiveRequest;
static inline AnyRequest getEmptyKey() {
return AnyRequest(AnyRequest::StorageKind::Empty);
}
static inline swift::AnyRequest getTombstoneKey() {
return AnyRequest(AnyRequest::StorageKind::Tombstone);
}
static unsigned getHashValue(const AnyRequest &request) {
return hash_value(request);
}
template <typename Request>
static unsigned getHashValue(const Request &request) {
return AnyRequest::hashForHolder(swift::TypeID<Request>::value,
hash_value(request));
}
static unsigned getHashValue(const ActiveRequest &request) {
return hash_value(request);
}
static bool isEqual(const AnyRequest &lhs, const AnyRequest &rhs) {
return lhs == rhs;
}
template <typename Request>
static bool isEqual(const Request &lhs, const AnyRequest &rhs) {
if (!rhs.hasStorage())
return false;
auto *rhsRequest = rhs.getAs<Request>();
return rhsRequest && lhs == *rhsRequest;
}
static bool isEqual(const ActiveRequest &lhs, const AnyRequest &rhs) {
return rhs.isStorageEqual(lhs);
}
};
} // end namespace llvm
#endif // SWIFT_AST_ANYREQUEST_H