| //===--- 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); |
| |
| // Define data flow bits for use in the AccessEnforcementDom pass. Each |
| // begin_access in the function is mapped to one instance of this subclass. |
| SWIFT_INLINE_BITFIELD(DomAccessedStorage, AccessedStorage, 1 + 1, |
| isInner : 1, containsRead : 1); |
| } 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); } |
| |
| // Clear any bits reserved for subclass data. Useful for up-casting back to |
| // the base class. |
| void resetSubclassData() { initKind(getKind()); } |
| |
| 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; |
| } |
| |
| /// Return true if the given storage objects have identical storage locations. |
| /// |
| /// This compares only the AccessedStorage base class bits, ignoring the |
| /// subclass bits. It is used for hash lookup equality. |
| 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; |
| } |
| } |
| |
| /// 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; |
| }; |
| |
| 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 LHS.hasIdenticalBase(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 |