mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
This is the first in a sequence of patches that implement various optimizations to transform load [copy] into load_borrow. The optimization works by looking for a load [copy] that: 1. Only has destroy_value as consuming users. This implies that we do not need to pass off the in memory value at +1 and that we can use a +0 value. 2. Is loading from a memory location that is never written to or only written to after all uses of the load [copy]. and then RAUW the load [copy] with a load_borrow and convertes the destroy_value to end_borrow. NOTE: I also a .def file for AccessedStorage so we can do visitors over the kinds. The reason I want to do this is to ensure that people update these optimizations if we add new storage kinds.
472 lines
17 KiB
C++
472 lines
17 KiB
C++
//===--- MemAccessUtils.h - Utilities for SIL memory access. ----*- C++ -*-===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2018 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
|
|
//
|
|
// These utilities live in SIL/ so they be used by SIL verification.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SWIFT_SIL_MEMACCESSUTILS_H
|
|
#define SWIFT_SIL_MEMACCESSUTILS_H
|
|
|
|
#include "swift/SIL/Projection.h"
|
|
#include "swift/SIL/InstructionUtils.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
|
|
namespace swift {
|
|
|
|
// stripAddressAccess() is declared in InstructionUtils.h.
|
|
|
|
inline bool accessKindMayConflict(SILAccessKind a, SILAccessKind b) {
|
|
return !(a == SILAccessKind::Read && b == SILAccessKind::Read);
|
|
}
|
|
|
|
/// Represents the identity of a stored class property as a combination
|
|
/// of a base, projection and projection path.
|
|
/// we pre-compute the base and projection path, even though we can
|
|
/// lazily do so, because it is more expensive otherwise
|
|
/// We lazily compute the projection path,
|
|
/// In the rare occasions we need it, because of Its destructor and
|
|
/// its non-trivial copy constructor
|
|
class ObjectProjection {
|
|
SILValue object;
|
|
const RefElementAddrInst *REA;
|
|
Projection proj;
|
|
|
|
public:
|
|
ObjectProjection(const RefElementAddrInst *REA)
|
|
: object(stripBorrow(REA->getOperand())), REA(REA),
|
|
proj(Projection(REA)) {}
|
|
|
|
ObjectProjection(SILValue object, const RefElementAddrInst *REA)
|
|
: object(object), REA(REA), proj(Projection(REA)) {}
|
|
|
|
const RefElementAddrInst *getInstr() const { return REA; }
|
|
|
|
SILValue getObject() const { return object; }
|
|
|
|
const Projection &getProjection() const { return proj; }
|
|
|
|
const Optional<ProjectionPath> getProjectionPath() const {
|
|
return ProjectionPath::getProjectionPath(stripBorrow(REA->getOperand()),
|
|
REA);
|
|
}
|
|
|
|
bool operator==(const ObjectProjection &other) const {
|
|
return getObject() == other.getObject() &&
|
|
getProjection() == other.getProjection();
|
|
}
|
|
|
|
bool operator!=(const ObjectProjection &other) const {
|
|
return !operator==(other);
|
|
}
|
|
};
|
|
|
|
/// Represents the identity of a storage object being accessed.
|
|
///
|
|
/// AccessedStorage may be one of several kinds of "identified" storage
|
|
/// objects, or may be valid, but Unidentified storage. An identified object
|
|
/// is known to identify the base of the accessed storage, whether that is a
|
|
/// SILValue that produces the base address, or a variable
|
|
/// declaration. "Uniquely identified" storage refers to identified storage that
|
|
/// cannot be aliased. For example, local allocations are uniquely identified,
|
|
/// while global variables and class properties are not. Unidentified storage is
|
|
/// associated with a SILValue that produces the accessed address but has not
|
|
/// been determined to be the base of a storage object. It may, for example,
|
|
/// be a SILPhiArgument.
|
|
///
|
|
/// An invalid AccessedStorage object is marked Unidentified and contains an
|
|
/// invalid value. This signals that analysis has failed to recognize an
|
|
/// expected address producer pattern. Over time, more aggressive
|
|
/// SILVerification could allow the optimizer to aggressively assert that
|
|
/// AccessedStorage is always valid.
|
|
///
|
|
/// Note that the SILValue that represents a storage object is not
|
|
/// necessarilly an address type. It may instead be a SILBoxType.
|
|
///
|
|
/// AccessedStorage hashing and comparison (via DenseMapInfo) is used to
|
|
/// determine when two 'begin_access' instructions access the same or disjoint
|
|
/// underlying objects.
|
|
///
|
|
/// `DenseMapInfo::isEqual()` guarantees that two AccessStorage values refer to
|
|
/// the same memory if both values are valid.
|
|
///
|
|
/// `!DenseMapInfo::isEqual()` does not guarantee that two identified
|
|
/// AccessStorage values are distinct. Inequality does, however, guarantee that
|
|
/// two *uniquely* identified AccessStorage values are distinct.
|
|
class AccessedStorage {
|
|
public:
|
|
/// Enumerate over all valid begin_access bases. Clients can use a covered
|
|
/// switch to warn if findAccessedAddressBase ever adds a case.
|
|
enum Kind : uint8_t {
|
|
#define ACCESSED_STORAGE(Name) Name,
|
|
#define ACCESSED_STORAGE_RANGE(Name, Start, End) \
|
|
First_##Name = Start, Last_##Name = End,
|
|
#include "swift/SIL/AccessedStorage.def"
|
|
NumKindBits = countBitsUsed(unsigned(Last_AccessedStorageKind))
|
|
};
|
|
|
|
static const char *getKindName(Kind k);
|
|
|
|
/// If the given address source is an identified access base, return the kind
|
|
/// of access base. Otherwise, return Unidentified.
|
|
static AccessedStorage::Kind classify(SILValue base);
|
|
|
|
protected:
|
|
// Form a bitfield that is effectively a union over any pass-specific data
|
|
// with the fields used within this class as a common prefix.
|
|
//
|
|
// This allows passes to embed analysis flags, and reserves enough space to
|
|
// embed a unique index.
|
|
//
|
|
// AccessedStorageAnalysis defines an StorageAccessInfo object that maps each
|
|
// storage object within a function to its unique storage index and summary
|
|
// information of that storage object.
|
|
//
|
|
// AccessEnforcementOpts defines an AccessEnforcementOptsInfo object that maps
|
|
// each begin_access to its storage object, unique access index, and summary
|
|
// info for that access.
|
|
union {
|
|
uint64_t OpaqueBits;
|
|
SWIFT_INLINE_BITFIELD_BASE(AccessedStorage, bitmax(NumKindBits, 8),
|
|
Kind : bitmax(NumKindBits, 8));
|
|
|
|
// Define bits for use in AccessedStorageAnalysis. Each identified storage
|
|
// object is mapped to one instance of this subclass.
|
|
SWIFT_INLINE_BITFIELD_FULL(StorageAccessInfo, AccessedStorage,
|
|
64 - NumAccessedStorageBits,
|
|
accessKind : NumSILAccessKindBits,
|
|
noNestedConflict : 1,
|
|
storageIndex : 64 - (NumAccessedStorageBits
|
|
+ NumSILAccessKindBits
|
|
+ 1));
|
|
|
|
// Define bits for use in the AccessEnforcementOpts pass. Each begin_access
|
|
// in the function is mapped to one instance of this subclass. Reserve a
|
|
// bit for a seenNestedConflict flag, which is the per-begin-access result
|
|
// of pass-specific analysis. The remaning bits are sufficient to index all
|
|
// begin_[unpaired_]access instructions.
|
|
//
|
|
// `AccessedStorage` refers to the AccessedStorageBitfield defined above,
|
|
// setting aside enough bits for common data.
|
|
SWIFT_INLINE_BITFIELD_FULL(AccessEnforcementOptsInfo, AccessedStorage,
|
|
64 - NumAccessedStorageBits,
|
|
seenNestedConflict : 1,
|
|
beginAccessIndex : 63 - NumAccessedStorageBits);
|
|
} Bits;
|
|
|
|
private:
|
|
union {
|
|
SILValue value;
|
|
unsigned paramIndex;
|
|
SILGlobalVariable *global;
|
|
ObjectProjection objProj;
|
|
};
|
|
|
|
void initKind(Kind k) {
|
|
Bits.OpaqueBits = 0;
|
|
Bits.AccessedStorage.Kind = k;
|
|
}
|
|
|
|
public:
|
|
AccessedStorage() : value() { initKind(Unidentified); }
|
|
|
|
AccessedStorage(SILValue base, Kind kind);
|
|
|
|
AccessedStorage(SILValue object, const RefElementAddrInst *REA)
|
|
: objProj(object, REA) {
|
|
initKind(Class);
|
|
}
|
|
|
|
// Return true if this is a valid storage object.
|
|
operator bool() const { return getKind() != Unidentified || value; }
|
|
|
|
Kind getKind() const { return static_cast<Kind>(Bits.AccessedStorage.Kind); }
|
|
|
|
SILValue getValue() const {
|
|
assert(getKind() != Argument && getKind() != Global && getKind() != Class);
|
|
return value;
|
|
}
|
|
|
|
unsigned getParamIndex() const {
|
|
assert(getKind() == Argument);
|
|
return paramIndex;
|
|
}
|
|
|
|
SILArgument *getArgument(SILFunction *F) const {
|
|
assert(getKind() == Argument);
|
|
return F->getArgument(paramIndex);
|
|
}
|
|
|
|
SILGlobalVariable *getGlobal() const {
|
|
assert(getKind() == Global);
|
|
return global;
|
|
}
|
|
|
|
const ObjectProjection &getObjectProjection() const {
|
|
assert(getKind() == Class);
|
|
return objProj;
|
|
}
|
|
|
|
bool hasIdenticalBase(const AccessedStorage &other) const {
|
|
if (getKind() != other.getKind())
|
|
return false;
|
|
|
|
switch (getKind()) {
|
|
case Box:
|
|
case Stack:
|
|
case Yield:
|
|
case Nested:
|
|
case Unidentified:
|
|
return value == other.value;
|
|
case Argument:
|
|
return paramIndex == other.paramIndex;
|
|
case Global:
|
|
return global == other.global;
|
|
case Class:
|
|
return objProj == other.objProj;
|
|
}
|
|
llvm_unreachable("unhandled kind");
|
|
}
|
|
|
|
/// Return true if the storage is guaranteed local.
|
|
bool isLocal() const {
|
|
switch (getKind()) {
|
|
case Box:
|
|
case Stack:
|
|
return true;
|
|
case Global:
|
|
case Class:
|
|
case Argument:
|
|
case Yield:
|
|
case Nested:
|
|
case Unidentified:
|
|
return false;
|
|
}
|
|
llvm_unreachable("unhandled kind");
|
|
}
|
|
|
|
bool isUniquelyIdentified() const {
|
|
switch (getKind()) {
|
|
case Box:
|
|
case Stack:
|
|
case Global:
|
|
return true;
|
|
case Class:
|
|
case Argument:
|
|
case Yield:
|
|
case Nested:
|
|
case Unidentified:
|
|
return false;
|
|
}
|
|
llvm_unreachable("unhandled kind");
|
|
}
|
|
|
|
bool isUniquelyIdentifiedOrClass() const {
|
|
if (isUniquelyIdentified())
|
|
return true;
|
|
return (getKind() == Class);
|
|
}
|
|
|
|
bool isDistinctFrom(const AccessedStorage &other) const {
|
|
if (isUniquelyIdentified() && other.isUniquelyIdentified()) {
|
|
return !hasIdenticalBase(other);
|
|
}
|
|
if (getKind() != Class || other.getKind() != Class) {
|
|
return false;
|
|
}
|
|
if (getObjectProjection().getProjection() ==
|
|
other.getObjectProjection().getProjection()) {
|
|
return false;
|
|
}
|
|
auto projPath = getObjectProjection().getProjectionPath();
|
|
auto otherProjPath = other.getObjectProjection().getProjectionPath();
|
|
if (!projPath.hasValue() || !otherProjPath.hasValue()) {
|
|
return false;
|
|
}
|
|
return projPath.getValue().hasNonEmptySymmetricDifference(
|
|
otherProjPath.getValue());
|
|
}
|
|
|
|
/// Returns the ValueDecl for the underlying storage, if it can be
|
|
/// determined. Otherwise returns null. For diagnostics and checking via the
|
|
/// ValueDecl if we are processing a `let` variable.
|
|
const ValueDecl *getDecl(SILFunction *F) const;
|
|
|
|
void print(raw_ostream &os) const;
|
|
void dump() const;
|
|
|
|
private:
|
|
// Disable direct comparison because we allow subclassing with bitfields.
|
|
// Currently, we use DenseMapInfo to unique storage, which defines key
|
|
// equalilty only in terms of the base AccessedStorage class bits.
|
|
bool operator==(const AccessedStorage &) const = delete;
|
|
bool operator!=(const AccessedStorage &) const = delete;
|
|
};
|
|
|
|
/// Return true if the given storage objects have identical storage locations.
|
|
///
|
|
/// This compares only the AccessedStorage base class bits, ignoring the
|
|
/// subclass bits.
|
|
inline bool accessingIdenticalLocations(AccessedStorage LHS,
|
|
AccessedStorage RHS) {
|
|
if (LHS.getKind() != RHS.getKind())
|
|
return false;
|
|
|
|
switch (LHS.getKind()) {
|
|
case swift::AccessedStorage::Box:
|
|
case swift::AccessedStorage::Stack:
|
|
case swift::AccessedStorage::Nested:
|
|
case swift::AccessedStorage::Yield:
|
|
case swift::AccessedStorage::Unidentified:
|
|
return LHS.getValue() == RHS.getValue();
|
|
case swift::AccessedStorage::Argument:
|
|
return LHS.getParamIndex() == RHS.getParamIndex();
|
|
case swift::AccessedStorage::Global:
|
|
return LHS.getGlobal() == RHS.getGlobal();
|
|
case swift::AccessedStorage::Class:
|
|
return LHS.getObjectProjection() == RHS.getObjectProjection();
|
|
}
|
|
}
|
|
|
|
template <class ImplTy, class ResultTy = void, typename... ArgTys>
|
|
class AccessedStorageVisitor {
|
|
ImplTy &asImpl() { return static_cast<ImplTy &>(*this); }
|
|
|
|
public:
|
|
#define ACCESSED_STORAGE(Name) \
|
|
ResultTy visit##Name(const AccessedStorage &storage, ArgTys &&... args);
|
|
#include "swift/SIL/AccessedStorage.def"
|
|
|
|
ResultTy visit(const AccessedStorage &storage, ArgTys &&... args) {
|
|
switch (storage.getKind()) {
|
|
#define ACCESSED_STORAGE(Name) \
|
|
case AccessedStorage::Name: \
|
|
return asImpl().visit##Name(storage, std::forward<ArgTys>(args)...);
|
|
#include "swift/SIL/AccessedStorage.def"
|
|
}
|
|
}
|
|
};
|
|
|
|
} // end namespace swift
|
|
|
|
namespace llvm {
|
|
|
|
/// Enable using AccessedStorage as a key in DenseMap.
|
|
/// Do *not* include any extra pass data in key equality.
|
|
template <> struct DenseMapInfo<swift::AccessedStorage> {
|
|
static swift::AccessedStorage getEmptyKey() {
|
|
return swift::AccessedStorage(swift::SILValue::getFromOpaqueValue(
|
|
llvm::DenseMapInfo<void *>::getEmptyKey()),
|
|
swift::AccessedStorage::Unidentified);
|
|
}
|
|
|
|
static swift::AccessedStorage getTombstoneKey() {
|
|
return swift::AccessedStorage(
|
|
swift::SILValue::getFromOpaqueValue(
|
|
llvm::DenseMapInfo<void *>::getTombstoneKey()),
|
|
swift::AccessedStorage::Unidentified);
|
|
}
|
|
|
|
static unsigned getHashValue(swift::AccessedStorage storage) {
|
|
switch (storage.getKind()) {
|
|
case swift::AccessedStorage::Box:
|
|
case swift::AccessedStorage::Stack:
|
|
case swift::AccessedStorage::Nested:
|
|
case swift::AccessedStorage::Yield:
|
|
case swift::AccessedStorage::Unidentified:
|
|
return DenseMapInfo<swift::SILValue>::getHashValue(storage.getValue());
|
|
case swift::AccessedStorage::Argument:
|
|
return storage.getParamIndex();
|
|
case swift::AccessedStorage::Global:
|
|
return DenseMapInfo<void *>::getHashValue(storage.getGlobal());
|
|
case swift::AccessedStorage::Class: {
|
|
const swift::ObjectProjection &P = storage.getObjectProjection();
|
|
return llvm::hash_combine(P.getObject(), P.getProjection());
|
|
}
|
|
}
|
|
llvm_unreachable("Unhandled AccessedStorageKind");
|
|
}
|
|
|
|
static bool isEqual(swift::AccessedStorage LHS, swift::AccessedStorage RHS) {
|
|
return swift::accessingIdenticalLocations(LHS, RHS);
|
|
}
|
|
};
|
|
|
|
} // end namespace llvm
|
|
|
|
namespace swift {
|
|
|
|
/// Given an address accessed by an instruction that reads or modifies
|
|
/// memory, return an AccessedStorage object that identifies the formal access.
|
|
///
|
|
/// The returned AccessedStorage represents the best attempt to find the base of
|
|
/// the storage object being accessed at `sourceAddr`. This may be a fully
|
|
/// identified storage base of known kind, or a valid but Unidentified storage
|
|
/// object, such as a SILPhiArgument.
|
|
///
|
|
/// This may return an invalid storage object if the address producer is not
|
|
/// recognized by a whitelist of recognizable access patterns. The result must
|
|
/// always be valid when `sourceAddr` is used for formal memory access, i.e. as
|
|
/// the operand of begin_access.
|
|
///
|
|
/// If `sourceAddr` is produced by a begin_access, this returns a Nested
|
|
/// AccessedStorage kind. This is useful for exclusivity checking to distinguish
|
|
/// between a nested access vs. a conflict.
|
|
AccessedStorage findAccessedStorage(SILValue sourceAddr);
|
|
|
|
/// Given an address accessed by an instruction that reads or modifies
|
|
/// memory, return an AccessedStorage that identifies the formal access, looking
|
|
/// through any Nested access to find the original storage.
|
|
///
|
|
/// This is identical to findAccessedStorage(), but never returns Nested
|
|
/// storage and may return invalid storage for nested access when the outer
|
|
/// access has Unsafe enforcement.
|
|
AccessedStorage findAccessedStorageNonNested(SILValue sourceAddr);
|
|
|
|
/// Return true if the given address operand is used by a memory operation that
|
|
/// initializes the memory at that address, implying that the previous value is
|
|
/// uninitialized.
|
|
bool memInstMustInitialize(Operand *memOper);
|
|
|
|
/// Return true if the given address producer may be the source of a formal
|
|
/// access (a read or write of a potentially aliased, user visible variable).
|
|
///
|
|
/// `storage` must be a valid AccessedStorage object.
|
|
///
|
|
/// If this returns false, then the address can be safely accessed without
|
|
/// a begin_access marker. To determine whether to emit begin_access:
|
|
/// storage = findAccessedStorage(address)
|
|
/// needsAccessMarker = storage && isPossibleFormalAccessBase(storage)
|
|
///
|
|
/// Warning: This is only valid for SIL with well-formed accessed. For example,
|
|
/// it will not handle address-type phis. Optimization passes after
|
|
/// DiagnoseStaticExclusivity may violate these assumptions.
|
|
bool isPossibleFormalAccessBase(const AccessedStorage &storage, SILFunction *F);
|
|
|
|
/// Visit each address accessed by the given memory operation.
|
|
///
|
|
/// This only visits instructions that modify memory in some user-visible way,
|
|
/// which could be considered part of a formal access.
|
|
void visitAccessedAddress(SILInstruction *I,
|
|
llvm::function_ref<void(Operand *)> visitor);
|
|
|
|
/// Perform a RAUW operation on begin_access with it's own source operand.
|
|
/// Then erase the begin_access and all associated end_access instructions.
|
|
/// Return an iterator to the following instruction.
|
|
///
|
|
/// The caller should use this iterator rather than assuming that the
|
|
/// instruction following this begin_access was not also erased.
|
|
SILBasicBlock::iterator removeBeginAccess(BeginAccessInst *beginAccess);
|
|
|
|
} // end namespace swift
|
|
|
|
#endif
|