| //===--- MemAccessUtils.h - Utilities for SIL memory access. ----*- C++ -*-===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2020 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 model the storage locations of memory access. See |
| /// ProgrammersGuide.md for high-level design. |
| /// |
| /// All memory operations that are part of a formal access, as defined by |
| /// exclusivity rules, are marked by begin_access and end_access instructions. |
| /// |
| /// Currently, access markers are stripped early in the pipeline. An active |
| /// goal is to require access markers in OSSA form, and to enable access |
| /// marker verification. |
| /// |
| /// To verify access markers, SIL checks that all memory operations either have |
| /// an address that originates in begin_access, or originates from a pattern |
| /// that is recognized as a non-formal-access. This implies that every SIL |
| /// memory operation has a recognizable address source. Given the address of a |
| /// memory operation, there are three levels of APIs that inspect the origin of |
| /// that address: |
| /// |
| /// 1. getTypedAccessAddress(): Find the originating address as close as |
| /// possible to the address of the formal access *without* looking past any |
| /// storage casts. This is useful when the type of the returned access address |
| /// must be consistent with the memory operation's type (the same type or a |
| /// parent type). For a formal access, this typically returns the begin_access, |
| /// but it is not guaranteed to because some accesses contain storage casts. For |
| /// non-formal access, it returns a best-effort address corresponding to the |
| /// base of an access. |
| /// |
| /// 2. getAccessScope(): If the memory operation is part of a formal access, |
| /// then this is guaranteed to return the begin_access marker. Otherwise, it |
| /// returns the best-effort address or pointer corresponding to the base of an |
| /// access. Useful to find the scope of a formal access. |
| /// |
| /// 3. getAccessBase(): Find the ultimate base of any address corresponding to |
| /// the accessed object, regardless of whether the address is nested within |
| /// access scopes, and regardless of any storage casts. This returns either an |
| /// address type, pointer type, or box type, but never a reference type. |
| /// Each object's property or its tail storage is separately accessed. |
| /// |
| /// For better identification an access base, use |
| /// AccessedStorage::compute(). It returns an AccessedStorage value |
| /// that identifies the storage of a memory access. It provides APIs |
| /// for inspecting type of accessed storage and allows for disambiguation |
| /// between different types of storage and different properties within a class. |
| /// |
| /// AccessedStorage::compute() follows the same logic as getAccessBase(), but if |
| /// the base is not recognized as a valid access, it returns invalid |
| /// AccessedStorage. It also performs further analysis to determine the root |
| /// reference of an object access. |
| /// |
| /// AccessedStorage::compute() returns the outermost AccessedStorage for any |
| /// memory address. It can be called on the address of a memory operation, the |
| /// address of a begin_access, or any other address value. If the address is |
| /// from an enforced begin_access or from any memory operation that is part of a |
| /// formal access, then it returns a valid AccessedStorage value. If the memory |
| /// operation is not part of a formal access, then it still identifies the |
| /// accessed storage as a best effort, but the result may be invalid storage. |
| /// |
| /// An active goal is to require compute() to always return a |
| /// valid AccessedStorage value even for operations that aren't part of a |
| /// formal access. |
| /// |
| /// The AccessEnforcementWMO pass is an example of an optimistic optimization |
| /// that relies on this requirement for correctness. If |
| /// AccessedStorage::compute() simply bailed out on an unrecognized memory |
| /// address by returning an invalid AccessedStorage, then the optimization could |
| /// make incorrect assumptions about the absence of access to globals or class |
| /// properties. |
| /// |
| /// AccessedStorage::computeInScope() returns an AccessedStorage value for the |
| /// immediately enclosing access scope. Within a formal access, it always |
| /// returns a Nested storage kind, which provides the begin_access marker. |
| /// |
| /// identifyFormalAccess() works like AccessedStorage::computeInScope(), but |
| /// finds the storage corresponding to a begin_access marker, rather than an |
| /// arbitrary address. This must return a valid AccessedStorage value unless the |
| /// access has "Unsafe" enforcement. The given begin_access marker may be nested |
| /// within another, outer access scope. For the purpose of exclusivity, nested |
| /// accesses are considered distinct formal accesses so they return distinct |
| /// AccessedStorage values even though they may access the same memory. This |
| /// way, nested accesses do not appear to conflict. |
| /// |
| /// AccessPath identifies both the accessed storage and the path to a specific |
| /// storage location within that storage object. See ProgrammersGuide.md and the |
| /// class comments below for details. AccessPath::compute() and |
| /// AccessPath::computeInScope() mirror the AccessedStorage API. |
| /// AccessPath::contains() and AccessPath::mayOverlap() provide efficient |
| /// comparison of access paths. |
| /// |
| /// AccessPath::collectUses() provides all reachable uses of the accessed |
| /// storage, allowing the selection of Exact, Inner, or Overlapping uses. |
| /// visitAccessedStorageUses() and visitAccessPathUses() generalize |
| /// handling of all reachable uses for a given storage location. |
| /// |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef SWIFT_SIL_MEMACCESSUTILS_H |
| #define SWIFT_SIL_MEMACCESSUTILS_H |
| |
| #include "swift/Basic/IndexTrie.h" |
| #include "swift/SIL/ApplySite.h" |
| #include "swift/SIL/InstructionUtils.h" |
| #include "swift/SIL/Projection.h" |
| #include "swift/SIL/SILArgument.h" |
| #include "swift/SIL/SILBasicBlock.h" |
| #include "swift/SIL/SILGlobalVariable.h" |
| #include "swift/SIL/SILInstruction.h" |
| #include "llvm/ADT/DenseMap.h" |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: Standalone API |
| //===----------------------------------------------------------------------===// |
| |
| namespace swift { |
| |
| /// Get the source address of a formal access by stripping access markers. |
| /// |
| /// Postcondition: If \p v is an address, then the returned value is also an |
| /// address (pointer-to-address is not stripped). |
| inline SILValue stripAccessMarkers(SILValue v) { |
| while (auto *bai = dyn_cast<BeginAccessInst>(v)) { |
| v = bai->getOperand(); |
| } |
| return v; |
| } |
| |
| /// Return the source address after stripping as many access projections as |
| /// possible without losing the address type. |
| /// |
| /// For formal accesses, this typically returns the begin_access, but may fail |
| /// for accesses that call into an addressor, which performs pointer |
| /// conversion. |
| /// |
| /// If there is no access marker, then this returns the "best-effort" address |
| /// corresponding to the accessed variable. This never looks through |
| /// pointer_to_address or other conversions that may change the address type |
| /// other than via type-safe (TBAA-compatible) projection. |
| SILValue getTypedAccessAddress(SILValue address); |
| |
| /// Return the source address or pointer after stripping all access projections |
| /// and storage casts. |
| /// |
| /// If this is a formal access, then it is guaranteed to return the immediately |
| /// enclosing begin_access and may "see through" storage casts to do so. |
| /// |
| /// If there is no access marker, then it returns a "best effort" address |
| /// corresponding to the accessed variable. In this case, the returned value |
| /// could be a non-address pointer type. |
| SILValue getAccessScope(SILValue address); |
| |
| /// Return the source address or pointer after stripping access projections, |
| /// access markers, and storage casts. |
| /// |
| /// The returned base address is guaranteed to match the unique AccessedStorage |
| /// value for the same \p address. That is, if two calls to getAccessBase() |
| /// return the same base address, then they must also have the same storage. |
| SILValue getAccessBase(SILValue address); |
| |
| /// Return true if \p address points to a let-variable. |
| /// |
| /// let-variables are only written during let-variable initialization, which is |
| /// assumed to store directly to the same, unaliased access base. |
| /// |
| /// The address of a let-variable must be the base of a formal access, not an |
| /// access projection. A 'let' member of a struct is *not* a let-variable, |
| /// because it's memory may be written when formally modifying the outer |
| /// struct. A let-variable is either an entire local variable, global variable, |
| /// or class property (these are all formal access base addresses). |
| bool isLetAddress(SILValue address); |
| |
| /// Return true if two accesses to the same storage may conflict given the kind |
| /// of each access. |
| inline bool accessKindMayConflict(SILAccessKind a, SILAccessKind b) { |
| return !(a == SILAccessKind::Read && b == SILAccessKind::Read); |
| } |
| |
| } // end namespace swift |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: AccessedStorage |
| //===----------------------------------------------------------------------===// |
| |
| namespace swift { |
| |
| /// Control def-use traversals, allowing them to remain with an access scope or |
| /// consider operations across scope boundaries. |
| enum class NestedAccessType { StopAtAccessBegin, IgnoreAccessBegin }; |
| |
| /// Exact uses only include uses whose AccessPath is identical to this one. |
| /// Inner uses have an AccessPath the same as or contained by this one. |
| /// Overlapping uses may contain, be contained by, or have an unknown |
| /// relationship with this one. An unknown relationship typically results from |
| /// a dynamic index_addr offset. |
| /// |
| /// The enum values are ordered. Each successive use type is a superset of the |
| /// previous. |
| enum class AccessUseType { Exact, Inner, Overlapping }; |
| |
| /// Represents the identity of a storage object being accessed. |
| /// |
| /// Requirements: |
| /// |
| /// A bitwise comparable encoding and hash key to identify each location |
| /// being formally accessed. Any two accesses of "uniquely identified" |
| /// storage must have the same key if they access the same storage and |
| /// distinct keys if they access distinct storage. For more efficient |
| /// analysis, accesses to non-uniquely identified storage should have the |
| /// same key if they may point to the same storage. |
| /// |
| /// Complete identification of all class or global accesses. Failing to |
| /// identify a class or global access will introduce undefined program |
| /// behavior which can't be tested. |
| /// |
| /// Memory operations on "uniquely identified" storage cannot overlap with any |
| /// other memory operation on distinct "uniquely identified" storage. |
| /// |
| /// AccessedStorage may be one of several kinds of "identified" storage |
| /// objects. Storage is "identified" when the base of the formal access is |
| /// recognized and the kind of storage precisely identified. The base is usually |
| /// represented by the SILValue that the memory address is derived from. For |
| /// global variable access, the base is the global's declaration instead. |
| /// |
| /// Unidentified *valid* storage is also associated with a SILValue that |
| /// produces the accessed address but that value has not been determined to be |
| /// the base of a formal access. It may be from a ref_tail_addr, undef, or some |
| /// recognized memory initialization pattern. Unidentified valid storage cannot |
| /// represent any arbitrary base address--it must at least been proven not to |
| /// correspond to any class or global variable access, unless it's nested within |
| /// another access to the same object. So, Unidentified can overlap with |
| /// Class/Global access, but it cannot be the only formal access to that memory. |
| /// |
| /// An *invalid* AccessedStorage object is Unidentified and associated with an |
| /// invalid SILValue. This signals that analysis has failed to recognize an |
| /// expected address producer pattern. |
| /// |
| /// An active goal is to enforce that every memory operation's |
| /// AccessedStorage is either valid or explicitly guarded by an "unsafe" |
| /// begin_access. |
| /// |
| /// Note that the SILValue that represents a storage object is not |
| /// necessarilly an address type. It may instead be a SILBoxType. So, even |
| /// though address phis are not allowed, finding the base of an access may |
| /// require traversing phis. |
| /// |
| /// Support for integer IDs and bitsets. An AccessedStorage value has enough |
| /// extra bits to store a unique index for each identified access in a |
| /// function. An AccessedStorage (without an ID) can be cheaply formed |
| /// on-the-fly for any memory operation then used as a hash key to lookup its |
| /// unique integer index which is stored directly in the hashed value but not |
| /// used as part of the hash key. |
| class AccessedStorage { |
| public: |
| /// Enumerate over all valid begin_access bases. Clients can use a covered |
| /// switch to warn if AccessedStorage ever adds a case. |
| enum Kind : uint8_t { |
| Box, |
| Stack, |
| Global, |
| Class, |
| Tail, |
| Argument, |
| Yield, |
| Nested, |
| Unidentified, |
| NumKindBits = countBitsUsed(static_cast<unsigned>(Unidentified)) |
| }; |
| |
| static const char *getKindName(Kind k); |
| |
| // Give object tail storage a fake large property index for convenience. |
| static constexpr unsigned TailIndex = std::numeric_limits<int>::max(); |
| |
| /// Directly create an AccessedStorage for class or tail property access. |
| static AccessedStorage forClass(SILValue object, unsigned propertyIndex) { |
| AccessedStorage storage; |
| if (propertyIndex == TailIndex) |
| storage.initKind(Tail); |
| else |
| storage.initKind(Class, propertyIndex); |
| storage.value = object; |
| return storage; |
| } |
| |
| /// Return an AccessedStorage value that best identifies a formally accessed |
| /// variable pointed to by \p sourceAddress, looking through any nested |
| /// formal accesses to find the underlying storage. |
| /// |
| /// \p sourceAddress may be an address, pointer, or box type. |
| /// |
| /// If \p sourceAddress is within a formal access scope, which does not have |
| /// "Unsafe" enforcement, then this always returns valid storage. |
| /// |
| /// If \p sourceAddress is not within a formal access scope, or within an |
| /// "Unsafe" scope, then this finds the formal storage if possible, otherwise |
| /// returning invalid storage. |
| static AccessedStorage compute(SILValue sourceAddress); |
| |
| /// Return an AccessedStorage object that identifies formal access scope that |
| /// immediately encloses \p sourceAddress. |
| /// |
| /// \p sourceAddress may be an address, pointer, or box type. |
| /// |
| /// If \p sourceAddress is within a formal access scope, this always returns a |
| /// valid "Nested" storage value. |
| /// |
| /// If \p sourceAddress is not within a formal access scope, then this finds |
| /// the formal storage if possible, otherwise returning invalid storage. |
| static AccessedStorage computeInScope(SILValue sourceAddress); |
| |
| protected: |
| // Checking the storage kind is far more common than other fields. Make sure |
| // it can be byte load with no shift. |
| static const int ReservedKindBits = 7; |
| static_assert(ReservedKindBits >= NumKindBits, "Too many storage kinds."); |
| |
| static const unsigned InvalidElementIndex = |
| (1 << (32 - (ReservedKindBits + 1))) - 1; |
| |
| // 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; |
| // elementIndex can overflow while gracefully degrading analysis. For now, |
| // reserve an absurd number of bits at a nice alignment boundary, but this |
| // can be reduced. |
| SWIFT_INLINE_BITFIELD_BASE(AccessedStorage, 32, |
| kind : ReservedKindBits, |
| isLet : 1, |
| elementIndex : 32 - (ReservedKindBits + 1)); |
| |
| // 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, |
| seenIdenticalStorage : 1, |
| beginAccessIndex : 62 - 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 { |
| // For non-class storage, 'value' is the access base. For class storage |
| // 'value' is the object base, where the access base is the class' stored |
| // property. For tail storage 'value' is the object base and there is no |
| // value for the access base. |
| SILValue value; |
| SILGlobalVariable *global; |
| }; |
| |
| void initKind(Kind k, unsigned elementIndex = InvalidElementIndex) { |
| Bits.opaqueBits = 0; |
| Bits.AccessedStorage.kind = k; |
| Bits.AccessedStorage.elementIndex = elementIndex; |
| } |
| |
| unsigned getElementIndex() const { return Bits.AccessedStorage.elementIndex; } |
| void setElementIndex(unsigned elementIndex) { |
| Bits.AccessedStorage.elementIndex = elementIndex; |
| } |
| |
| public: |
| AccessedStorage() : value() { initKind(Unidentified); } |
| |
| AccessedStorage(SILValue base, Kind kind); |
| |
| // 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(), Bits.AccessedStorage.elementIndex); |
| } |
| |
| SILValue getValue() const { |
| assert(getKind() != Global && getKind() != Class && getKind() != Tail); |
| return value; |
| } |
| |
| unsigned getParamIndex() const { |
| assert(getKind() == Argument); |
| return getElementIndex(); |
| } |
| |
| SILFunctionArgument *getArgument() const { |
| assert(getKind() == Argument); |
| return cast<SILFunctionArgument>(value); |
| } |
| |
| SILGlobalVariable *getGlobal() const { |
| assert(getKind() == Global); |
| return global; |
| } |
| |
| bool isReference() const { return getKind() == Class || getKind() == Tail; } |
| |
| SILValue getObject() const { |
| assert(isReference()); |
| return value; |
| } |
| unsigned getPropertyIndex() const { |
| assert(getKind() == Class); |
| return getElementIndex(); |
| } |
| |
| /// Return the address or reference root that the storage was based |
| /// on. Returns an invalid SILValue for globals or invalid storage. |
| SILValue getRoot() const { |
| switch (getKind()) { |
| case AccessedStorage::Box: |
| case AccessedStorage::Stack: |
| case AccessedStorage::Nested: |
| case AccessedStorage::Argument: |
| case AccessedStorage::Yield: |
| case AccessedStorage::Unidentified: |
| return getValue(); // Can be invalid for Unidentified storage. |
| case AccessedStorage::Global: |
| return SILValue(); |
| case AccessedStorage::Class: |
| case AccessedStorage::Tail: |
| return getObject(); |
| } |
| } |
| |
| /// Visit all access roots. If any roots are visited then the original memory |
| /// operation access must be reachable from one of those roots. Unidentified |
| /// storage might not have any root. Identified storage always has at least |
| /// one root. Identified non-global storage always has a single root. For |
| /// Global storage, this visits all global_addr instructions in the function |
| /// that reference the same SILGlobalVariable. |
| /// |
| /// \p function must be non-null for Global storage (global_addr cannot |
| /// occur in a static initializer). |
| void |
| visitRoots(SILFunction *function, |
| llvm::function_ref<bool(SILValue)> visitor) const; |
| |
| /// 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, so it should not |
| /// perform any additional lookups or dereference memory outside itself. |
| bool hasIdenticalBase(const AccessedStorage &other) const { |
| if (getKind() != other.getKind()) |
| return false; |
| |
| switch (getKind()) { |
| case Box: |
| case Stack: |
| case Tail: |
| case Argument: |
| case Yield: |
| case Nested: |
| case Unidentified: |
| return value == other.value; |
| case Global: |
| return global == other.global; |
| case Class: |
| return value == other.value |
| && getElementIndex() == other.getElementIndex(); |
| } |
| llvm_unreachable("covered switch"); |
| } |
| |
| /// Return true if the storage is guaranteed local. |
| bool isLocal() const { |
| switch (getKind()) { |
| case Box: |
| case Stack: |
| return true; |
| case Global: |
| case Class: |
| case Tail: |
| case Argument: |
| case Yield: |
| case Nested: |
| case Unidentified: |
| return false; |
| } |
| llvm_unreachable("unhandled kind"); |
| } |
| |
| /// Return true if the given access is guaranteed to be within a heap object. |
| bool isObjectAccess() const { |
| return getKind() == Class || getKind() == Tail; |
| } |
| |
| /// Return true if the given access is on a 'let' lvalue. |
| bool isLetAccess() const { return Bits.AccessedStorage.isLet; } |
| |
| /// If this is a uniquely identified formal access, then it cannot |
| /// alias with any other uniquely identified access to different storage. |
| bool isUniquelyIdentified() const { |
| switch (getKind()) { |
| case Box: |
| case Stack: |
| case Global: |
| return true; |
| case Argument: |
| return |
| getArgument()->getArgumentConvention().isExclusiveIndirectParameter(); |
| case Class: |
| case Tail: |
| case Yield: |
| case Nested: |
| case Unidentified: |
| return false; |
| } |
| llvm_unreachable("unhandled kind"); |
| } |
| |
| /// Return true if this storage is guaranteed not to overlap with \p other's |
| /// storage. |
| bool isDistinctFrom(const AccessedStorage &other) const { |
| return isDistinctFrom<&AccessedStorage::isUniquelyIdentified>(other); |
| } |
| |
| /// Return true if this identifies the base of a formal access location. |
| /// |
| /// Most formal access bases are uniquely identified, but class access |
| /// may alias other references to the same object. |
| bool isFormalAccessBase() const { |
| if (isUniquelyIdentified()) |
| return true; |
| |
| return getKind() == Class; |
| } |
| |
| /// Returns the ValueDecl for the underlying storage, if it can be |
| /// determined. Otherwise returns null. |
| /// |
| /// If \p base is provided, then it must be the accessed base for this |
| /// storage, as passed to the AccessedStorage constructor. What \p base is |
| /// provided, this is guaranteed to return a valid decl for class properties; |
| /// otherwise it is only a best effort based on the type of the object root |
| /// *before* the object is cast to the final accessed reference type. |
| const ValueDecl *getDecl(SILValue base = SILValue()) const; |
| |
| /// Get all leaf uses of all address, pointer, or box values that have a this |
| /// AccessedStorage in common. Return true if all uses were found before |
| /// reaching the limit. |
| /// |
| /// The caller of 'collectUses' can determine the use type (exact, inner, or |
| /// overlapping) from the resulting \p uses list by checking 'accessPath == |
| /// usePath', accessPath.contains(usePath)', and |
| /// 'accessPath.mayOverlap(usePath)'. Alternatively, the client may call |
| /// 'visitAccessedStorageUses' with its own AccessUseVisitor subclass to |
| /// sort the use types. |
| bool |
| collectUses(SmallVectorImpl<Operand *> &uses, AccessUseType useTy, |
| SILFunction *function, |
| unsigned useLimit = std::numeric_limits<unsigned>::max()) 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 <bool (AccessedStorage::*IsUniqueFn)() const> |
| bool isDistinctFrom(const AccessedStorage &other) const { |
| if ((this->*IsUniqueFn)()) { |
| if ((other.*IsUniqueFn)() && !hasIdenticalBase(other)) |
| return true; |
| |
| if (other.isObjectAccess()) |
| return true; |
| |
| // We currently assume that Unidentified storage may overlap with |
| // Box/Stack storage. |
| return false; |
| } |
| if ((other.*IsUniqueFn)()) |
| return other.isDistinctFrom<IsUniqueFn>(*this); |
| |
| // Neither storage is uniquely identified. |
| if (isObjectAccess()) { |
| if (other.isObjectAccess()) { |
| // Property access cannot overlap with Tail access. |
| if (getKind() != other.getKind()) |
| return true; |
| |
| // We could also check if the object types are distinct, but that only |
| // helps if we know the relationships between class types. |
| return getKind() == Class |
| && getPropertyIndex() != other.getPropertyIndex(); |
| } |
| // Any type of nested/argument address may be within the same object. |
| // |
| // We also currently assume Unidentified access may be within an object |
| // purely to handle KeyPath accesses. The deriviation of the KeyPath |
| // address must separately appear to be a Class access so that all Class |
| // accesses are accounted for. |
| return false; |
| } |
| if (other.isObjectAccess()) |
| return other.isDistinctFrom<IsUniqueFn>(*this); |
| |
| // Neither storage is from a class or tail. |
| // |
| // Unidentified values may alias with each other or with any kind of |
| // nested/argument access. |
| return false; |
| } |
| |
| void setLetAccess(SILValue base); |
| }; |
| |
| } // end namespace swift |
| |
| namespace llvm { |
| |
| /// Enable using AccessedStorage as a key in DenseMap. |
| /// Do *not* include any extra pass data in key equality. |
| /// |
| /// AccessedStorage hashing and comparison 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. |
| 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: |
| return llvm::hash_combine(storage.getObject(), |
| storage.getPropertyIndex()); |
| case swift::AccessedStorage::Tail: |
| return DenseMapInfo<swift::SILValue>::getHashValue(storage.getObject()); |
| } |
| llvm_unreachable("Unhandled AccessedStorageKind"); |
| } |
| |
| static bool isEqual(swift::AccessedStorage LHS, swift::AccessedStorage RHS) { |
| return LHS.hasIdenticalBase(RHS); |
| } |
| }; |
| |
| } // namespace llvm |
| |
| namespace swift { |
| |
| /// For convenience, encapsulate and AccessedStorage value along with its |
| /// accessed base address. |
| struct AccessedStorageWithBase { |
| AccessedStorage storage; |
| // The base of the formal access. For class storage, it is the |
| // ref_element_addr. For global storage it is the global_addr or initializer |
| // apply. For other storage, it is the same as accessPath.getRoot(). |
| // |
| // Base may be invalid for global_addr -> address_to_pointer -> phi patterns. |
| // FIXME: add a structural requirement to SIL so base is always valid in OSSA. |
| SILValue base; |
| |
| AccessedStorageWithBase(AccessedStorage storage, SILValue base) |
| : storage(storage), base(base) {} |
| |
| /// Identical to AccessedStorage::compute but preserves the access base. |
| static AccessedStorageWithBase compute(SILValue sourceAddress); |
| |
| /// Identical to AccessedStorage::computeInScope but preserves the base. |
| static AccessedStorageWithBase computeInScope(SILValue sourceAddress); |
| }; |
| |
| /// Return an AccessedStorage value that identifies formally accessed storage |
| /// for \p beginAccess, considering any outer access scope as having distinct |
| /// storage from this access scope. This is useful for exclusivity checking |
| /// to distinguish between nested access vs. conflicting access on the same |
| /// storage. |
| /// |
| /// May return an invalid storage for either: |
| /// - A \p beginAccess with Unsafe enforcement |
| /// - Non-OSSA form in which address-type block args are allowed |
| inline AccessedStorage identifyFormalAccess(BeginAccessInst *beginAccess) { |
| return AccessedStorage::computeInScope(beginAccess->getSource()); |
| } |
| |
| inline AccessedStorage |
| identifyFormalAccess(BeginUnpairedAccessInst *beginAccess) { |
| return AccessedStorage::computeInScope(beginAccess->getSource()); |
| } |
| |
| } // end namespace swift |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: AccessPath |
| //===----------------------------------------------------------------------===// |
| |
| namespace swift { |
| |
| /// Identify an addressable location based the AccessedStorage and projection |
| /// path. |
| /// |
| /// Each unique path from a base address implies a unique memory location within |
| /// that object. A path prefix identifies memory that contains all paths with |
| /// the same prefix. The AccessPath returned by AccessPath::compute(address) |
| /// identifies the object seen by any memory operation that *directly* operates |
| /// on 'address'. The computed path is a prefix of the paths of any contained |
| /// subobjects. |
| /// |
| /// Path indices, encoded by AccessPath::Index, may be either subobject |
| /// projections or offset indices. We print subobject indices as '#n' and offset |
| /// indices as '@n'. |
| /// |
| /// Example Def->Use: (Path indices) |
| /// struct_element_addr #1: (#1) |
| /// ref_tail_addr -> struct_element_addr #2: (#2) |
| /// ref_tail_addr -> index_addr #1 -> struct_element_addr #2: (@1, #2) |
| /// pointer_to_address -> struct_element_addr #2: (#2) |
| /// pointer_to_address -> index_addr #1 -> struct_element_addr #2: (@1, #2) |
| /// |
| /// The index of ref_element_addr is part of the storage identity and does |
| /// not contribute to the access path indices. |
| /// |
| /// A well-formed path has at most one offset component at the begining of the |
| /// path (chained index_addrs are merged into one offset). In other words, |
| /// taking an offset from a subobject projection is not well-formed access |
| /// path. However, it is possible (however undesirable) for programmers to |
| /// convert a subobject address into a pointer (for example, via implicit |
| /// conversion), then advance that pointer. Since we can't absolutely prevent |
| /// this, we instead consider it an invalid AccessPath. This is the only case in |
| /// which AccessPath::storage can differ from AccessedStorage::compute(). |
| /// |
| /// Storing an AccessPath ammortizes to constant space. To cache identification |
| /// of address locations, AccessPath should be used rather than the |
| /// ProjectionPath which requires quadratic space in the number of address |
| /// values and quadratic time when comparing addresses. |
| /// |
| /// Type-cast operations such as address_to_pointer may appear on the access |
| /// path. It is illegal to use these operations to cast to a non-layout |
| /// compatible type. TODO: add enforcement for this rule. |
| class AccessPath { |
| public: |
| /// Compute the access path at \p address. This ignores begin_access markers, |
| /// returning the outermost AccessedStorage. |
| /// |
| /// The computed access path corresponds to the subobject for a memory |
| /// operation that directly operates on \p address; so, for an indexable |
| /// address, this implies an operation at index zero. |
| static AccessPath compute(SILValue address); |
| |
| /// Compute the access path at \p address. If \p address is within a formal |
| /// access, then AccessStorage will have a nested type and base will be a |
| /// begin_access marker. |
| /// |
| /// This is primarily useful for recovering the access scope. The original |
| /// storage kind will only be discovered when \p address is part of a formal |
| /// access, thus not within an access scope. |
| static AccessPath computeInScope(SILValue address); |
| |
| // Encode a dynamic index_addr as an UnknownOffset. |
| static constexpr int UnknownOffset = std::numeric_limits<int>::min() >> 1; |
| |
| struct PathNode; |
| |
| // An access path index. |
| // |
| // Note: |
| // - IndexTrieNode::RootIndex = INT_MIN = 0x80000000 |
| // - AccessedStorage::TailIndex = INT_MAX = 0x7FFFFFFF |
| // - AccessPath::UnknownOffset = (INT_MIN>>1) = 0xC0000000 |
| // - An offset index is never zero |
| class Index { |
| public: |
| friend struct PathNode; |
| |
| // Use the sign bit to identify offset indices. Subobject projections are |
| // always positive. |
| constexpr static unsigned IndexFlag = unsigned(1) << 31; |
| static int encodeOffset(int indexValue) { |
| assert(indexValue != 0 && "an offset index cannot be zero"); |
| // Must be able to sign-extended the 31-bit value. |
| assert(((indexValue << 1) >> 1) == indexValue); |
| return indexValue | IndexFlag; |
| } |
| |
| // Encode a positive field index, property index, or TailIndex. |
| static Index forSubObjectProjection(unsigned projIdx) { |
| assert(Index(projIdx).isSubObjectProjection()); |
| return Index(projIdx); |
| } |
| |
| static Index forOffset(unsigned projIdx) { |
| return Index(encodeOffset(projIdx)); |
| } |
| |
| private: |
| int indexEncoding; |
| Index(int indexEncoding) : indexEncoding(indexEncoding) {} |
| |
| public: |
| bool isSubObjectProjection() const { return indexEncoding >= 0; } |
| |
| int getSubObjectIndex() const { |
| assert(isSubObjectProjection()); |
| return indexEncoding; |
| } |
| |
| // Sign-extend the 31-bit value. |
| int getOffset() const { |
| assert(!isSubObjectProjection()); |
| return ((indexEncoding << 1) >> 1); |
| } |
| |
| bool isUnknownOffset() const { |
| return indexEncoding == AccessPath::UnknownOffset; |
| } |
| |
| int getEncoding() const { return indexEncoding; } |
| |
| void print(raw_ostream &os) const; |
| |
| void dump() const; |
| }; |
| |
| // A component of the AccessPath. |
| // |
| // Transient wrapper around the underlying IndexTrieNode that encodes either a |
| // subobject projection or an offset index. |
| struct PathNode { |
| IndexTrieNode *node = nullptr; |
| |
| constexpr PathNode() = default; |
| |
| PathNode(IndexTrieNode *node) : node(node) {} |
| |
| bool isValid() const { return node != nullptr; } |
| |
| bool isRoot() const { return node->isRoot(); } |
| |
| bool isLeaf() const { return node->isLeaf(); } |
| |
| Index getIndex() const { return Index(node->getIndex()); } |
| |
| PathNode getParent() const { return node->getParent(); } |
| |
| // Return the PathNode from \p subNode's path one level deeper than \p |
| // prefixNode. |
| // |
| // Precondition: this != subNode |
| PathNode findPrefix(PathNode subNode) const; |
| |
| bool operator==(PathNode other) const { return node == other.node; } |
| bool operator!=(PathNode other) const { return node != other.node; } |
| }; |
| |
| private: |
| AccessedStorage storage; |
| PathNode pathNode; |
| // store the single offset index independent from the PathNode to simplify |
| // checking for path overlap. |
| int offset = 0; |
| |
| public: |
| // AccessPaths are built by AccessPath::compute(address). |
| // |
| // AccessedStorage is only used to identify the storage location; AccessPath |
| // ignores its subclass bits. |
| AccessPath(AccessedStorage storage, PathNode pathNode, int offset) |
| : storage(storage), pathNode(pathNode), offset(offset) { |
| assert(storage.getKind() != AccessedStorage::Nested); |
| assert(pathNode.isValid() || !storage && "Access path requires a pathNode"); |
| } |
| |
| AccessPath() = default; |
| |
| bool operator==(AccessPath other) const { |
| return |
| storage.hasIdenticalBase(other.storage) && pathNode == other.pathNode; |
| } |
| bool operator!=(AccessPath other) const { return !(*this == other); } |
| |
| bool isValid() const { return pathNode.isValid(); } |
| |
| AccessedStorage getStorage() const { return storage; } |
| |
| PathNode getPathNode() const { return pathNode; } |
| |
| int getOffset() const { return offset; } |
| |
| bool hasUnknownOffset() const { return offset == UnknownOffset; } |
| |
| /// Return true if this path contains \p subPath. |
| /// |
| /// Identical AccessPath's contain each other. |
| /// |
| /// Returns false if either path is invalid. |
| bool contains(AccessPath subPath) const; |
| |
| /// Return true if this path may overlap with \p otherPath. |
| /// |
| /// Returns true if either path is invalid. |
| bool mayOverlap(AccessPath otherPath) const; |
| |
| /// Return the address root that the access path was based on. Returns |
| /// an invalid SILValue for globals or invalid storage. |
| SILValue getRoot() const { return storage.getRoot(); } |
| |
| /// Get all leaf uses of all address, pointer, or box values that have a this |
| /// AccessedStorage in common. Return true if all uses were found before |
| /// reaching the limit. |
| /// |
| /// The caller of 'collectUses' can determine the use type (exact, inner, or |
| /// overlapping) from the resulting \p uses list by checking 'accessPath == |
| /// usePath', accessPath.contains(usePath)', and |
| /// 'accessPath.mayOverlap(usePath)'. Alternatively, the client may call |
| /// 'visitAccessPathUses' with its own AccessUseVisitor subclass to |
| /// sort the use types. |
| bool |
| collectUses(SmallVectorImpl<Operand *> &uses, AccessUseType useTy, |
| SILFunction *function, |
| unsigned useLimit = std::numeric_limits<unsigned>::max()) const; |
| |
| void printPath(raw_ostream &os) const; |
| void print(raw_ostream &os) const; |
| void dump() const; |
| }; |
| |
| // Encapsulate the result of computing an AccessPath. AccessPath does not store |
| // the base address of the formal access because it does not always uniquely |
| // indentify the access, but AccessPath users may use the base address to to |
| // recover the def-use chain for a specific global_addr or ref_element_addr. |
| struct AccessPathWithBase { |
| AccessPath accessPath; |
| // The address-type value that is the base of the formal access. For |
| // class storage, it is the ref_element_addr. For global storage it is the |
| // global_addr or initializer apply. For other storage, it is the same as |
| // accessPath.getRoot(). |
| // |
| // Note: base may be invalid for global_addr -> address_to_pointer -> phi |
| // patterns, while the accessPath is still valid. |
| // |
| // FIXME: add a structural requirement to SIL so base is always valid in OSSA. |
| SILValue base; |
| |
| /// Compute the access path at \p address, and record the access base. This |
| /// ignores begin_access markers, returning the outermost AccessedStorage. |
| static AccessPathWithBase compute(SILValue address); |
| |
| /// Compute the access path at \p address, and record the access base. If \p |
| /// address is within a formal access, then AccessStorage will have a nested |
| /// type and base will be a begin_access marker. |
| static AccessPathWithBase computeInScope(SILValue address); |
| |
| AccessPathWithBase(AccessPath accessPath, SILValue base) |
| : accessPath(accessPath), base(base) {} |
| |
| bool operator==(AccessPathWithBase other) const { |
| return accessPath == other.accessPath && base == other.base; |
| } |
| bool operator!=(AccessPathWithBase other) const { return !(*this == other); } |
| |
| void print(raw_ostream &os) const; |
| void dump() const; |
| }; |
| |
| inline AccessPath AccessPath::compute(SILValue address) { |
| return AccessPathWithBase::compute(address).accessPath; |
| } |
| |
| inline AccessPath AccessPath::computeInScope(SILValue address) { |
| return AccessPathWithBase::compute(address).accessPath; |
| } |
| |
| } // end namespace swift |
| |
| namespace llvm { |
| |
| /// Allow AccessPath to be used in DenseMap. |
| template <> struct DenseMapInfo<swift::AccessPath> { |
| static inline swift::AccessPath getEmptyKey() { |
| return swift::AccessPath( |
| DenseMapInfo<swift::AccessedStorage>::getEmptyKey(), |
| swift::AccessPath::PathNode( |
| DenseMapInfo<swift::IndexTrieNode *>::getEmptyKey()), 0); |
| } |
| static inline swift::AccessPath getTombstoneKey() { |
| return swift::AccessPath( |
| DenseMapInfo<swift::AccessedStorage>::getTombstoneKey(), |
| swift::AccessPath::PathNode( |
| DenseMapInfo<swift::IndexTrieNode *>::getTombstoneKey()), 0); |
| } |
| static inline unsigned getHashValue(const swift::AccessPath &val) { |
| return llvm::hash_combine( |
| DenseMapInfo<swift::AccessedStorage>::getHashValue(val.getStorage()), |
| val.getPathNode().node); |
| } |
| static bool isEqual(const swift::AccessPath &lhs, |
| const swift::AccessPath &rhs) { |
| return lhs == rhs; |
| } |
| }; |
| template <> struct DenseMapInfo<swift::AccessPathWithBase> { |
| static inline swift::AccessPathWithBase getEmptyKey() { |
| return swift::AccessPathWithBase( |
| DenseMapInfo<swift::AccessPath>::getEmptyKey(), |
| DenseMapInfo<swift::SILValue>::getEmptyKey()); |
| } |
| static inline swift::AccessPathWithBase getTombstoneKey() { |
| return swift::AccessPathWithBase( |
| DenseMapInfo<swift::AccessPath>::getTombstoneKey(), |
| DenseMapInfo<swift::SILValue>::getTombstoneKey()); |
| } |
| static inline unsigned getHashValue(const swift::AccessPathWithBase &val) { |
| return llvm::hash_combine( |
| DenseMapInfo<swift::AccessPath>::getHashValue(val.accessPath), |
| DenseMapInfo<swift::SILValue>::getHashValue(val.base)); |
| } |
| static bool isEqual(const swift::AccessPathWithBase &lhs, |
| const swift::AccessPathWithBase &rhs) { |
| return lhs == rhs; |
| } |
| }; |
| |
| } // end namespace llvm |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: Use visitors |
| //===----------------------------------------------------------------------===// |
| |
| namespace swift { |
| |
| /// Interface to the customizable use visitor. |
| struct AccessUseVisitor { |
| AccessUseType useTy; |
| NestedAccessType nestedAccessTy; |
| |
| AccessUseVisitor(AccessUseType useTy, NestedAccessType nestedTy) |
| : useTy(useTy), nestedAccessTy(nestedTy) {} |
| |
| virtual ~AccessUseVisitor() {} |
| |
| bool findInnerUses() const { return useTy >= AccessUseType::Inner; } |
| bool findOverlappingUses() const { |
| return useTy == AccessUseType::Overlapping; |
| } |
| |
| bool visitExactUse(Operand *use) { |
| return visitUse(use, AccessUseType::Exact); |
| } |
| bool visitInnerUse(Operand *use) { |
| return findInnerUses() ? visitUse(use, AccessUseType::Inner) : true; |
| } |
| bool visitOverlappingUse(Operand *use) { |
| return |
| findOverlappingUses() ? visitUse(use, AccessUseType::Overlapping) : true; |
| } |
| |
| virtual bool visitUse(Operand *use, AccessUseType useTy) = 0; |
| }; |
| |
| /// Visit all uses of \p storage. |
| /// |
| /// Return true if all uses were collected. This is always true as long the \p |
| /// visitor's visitUse method returns true. |
| bool visitAccessedStorageUses(AccessUseVisitor &visitor, |
| AccessedStorage storage, |
| SILFunction *function); |
| |
| /// Visit the uses of \p accessPath. |
| /// |
| /// If the storage kind is Global, then function must be non-null (global_addr |
| /// only occurs inside SILFunction). |
| /// |
| /// Return true if all uses were collected. This is always true as long the \p |
| /// visitor's visitUse method returns true. |
| bool visitAccessPathUses(AccessUseVisitor &visitor, AccessPath accessPath, |
| SILFunction *function); |
| |
| } // end namespace swift |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: Helper API for specific formal access patterns |
| //===----------------------------------------------------------------------===// |
| |
| namespace swift { |
| |
| /// 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); |
| |
| /// Is this an alloc_stack instruction that is: |
| /// |
| /// 1. Only initialized once in its own def block. |
| /// 2. Never written to again except by destroy_addr. |
| /// |
| /// On return, destroyingUsers contains the list of users that destroy the |
| /// alloc_stack. If the alloc_stack is destroyed in pieces, we do not guarantee |
| /// that the list of destroying users is a minimal jointly post-dominating set. |
| bool isSingleInitAllocStack(AllocStackInst *asi, |
| SmallVectorImpl<Operand *> &destroyingUses); |
| |
| /// Return true if the given address value is produced by a special address |
| /// producer that is only used for local initialization, not formal access. |
| bool isAddressForLocalInitOnly(SILValue sourceAddr); |
| |
| /// Return true if the given apply invokes a global addressor defined in another |
| /// module. |
| bool isExternalGlobalAddressor(ApplyInst *AI); |
| |
| /// Return true if the given StructExtractInst extracts the RawPointer from |
| /// Unsafe[Mutable]Pointer. |
| bool isUnsafePointerExtraction(StructExtractInst *SEI); |
| |
| /// Given a block argument address base, check if it is actually a box projected |
| /// from a switch_enum. This is a valid pattern at any SIL stage resulting in a |
| /// block-type phi. In later SIL stages, the optimizer may form address-type |
| /// phis, causing this assert if called on those cases. |
| void checkSwitchEnumBlockArg(SILPhiArgument *arg); |
| |
| /// 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, non-nested 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 = identifyFormalAccess(address) |
| /// needsAccessMarker = storage && isPossibleFormalAccessBase(storage) |
| /// |
| /// Warning: This is only valid for SIL with well-formed accesses. For example, |
| /// it will not handle address-type phis. Optimization passes after |
| /// DiagnoseStaticExclusivity may violate these assumptions. |
| /// |
| /// This is not a member of AccessedStorage because it only makes sense to use |
| /// in SILGen before access markers are emitted, or when verifying access |
| /// markers. |
| bool isPossibleFormalAccessBase(const AccessedStorage &storage, |
| SILFunction *F); |
| |
| /// 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 |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: AccessUseDefChainVisitor |
| //===----------------------------------------------------------------------===// |
| |
| namespace swift { |
| |
| /// Return true if \p svi is a cast that preserves the identity and |
| /// reference-counting equivalence of the reference at operand zero. |
| bool isRCIdentityPreservingCast(SingleValueInstruction *svi); |
| |
| /// If \p svi is an access projection, return an address-type operand for the |
| /// incoming address. |
| /// |
| /// An access projection is on the inside of a formal access. It includes |
| /// struct_element_addr and tuple_element_addr, but not ref_element_addr. |
| /// |
| /// The returned address may point to any compatible type, which may alias with |
| /// the projected address. Arbitrary address casts are not allowed. |
| inline Operand *getAccessProjectionOperand(SingleValueInstruction *svi) { |
| switch (svi->getKind()) { |
| default: |
| return nullptr; |
| |
| case SILInstructionKind::StructElementAddrInst: |
| case SILInstructionKind::TupleElementAddrInst: |
| case SILInstructionKind::IndexAddrInst: |
| case SILInstructionKind::TailAddrInst: |
| case SILInstructionKind::InitEnumDataAddrInst: |
| // open_existential_addr and unchecked_take_enum_data_addr are problematic |
| // because they both modify memory and are access projections. Ideally, they |
| // would not be casts, but will likely be eliminated with opaque values. |
| case SILInstructionKind::OpenExistentialAddrInst: |
| case SILInstructionKind::UncheckedTakeEnumDataAddrInst: |
| return &svi->getAllOperands()[0]; |
| |
| // Special-case this indirect enum pattern: |
| // unchecked_take_enum_data_addr -> load -> project_box |
| // (the individual load and project_box are not access projections) |
| // |
| // FIXME: Make sure this case goes away with OSSA and opaque values. If not, |
| // then create a special instruction for this pattern. That way we have an |
| // invariant that all access projections are single-value address-to-address |
| // conversions. Then reuse this helper for both use-def an def-use traversals. |
| // |
| // Check getAccessProjectionOperand() before isAccessedStorageCast() because |
| // it will consider any project_box to be a storage cast. |
| case SILInstructionKind::ProjectBoxInst: |
| if (auto *load = dyn_cast<LoadInst>(svi->getOperand(0))) |
| return &load->getOperandRef(); |
| |
| return nullptr; |
| }; |
| } |
| |
| /// An address, pointer, or box cast that occurs outside of the formal |
| /// access. These convert the base of accessed storage without affecting the |
| /// AccessPath. Useful for both use-def and def-use traversal. The source |
| /// address must be at operand(0). |
| /// |
| /// Some of these casts, such as address_to_pointer, may also occur inside of a |
| /// formal access. TODO: Add stricter structural guarantee such that these never |
| /// occur within an access. It's important to be able to get the accessed |
| /// address without looking though type casts or pointer_to_address [strict], |
| /// which we can't do if those operations are behind access projections. |
| inline bool isAccessedStorageCast(SingleValueInstruction *svi) { |
| switch (svi->getKind()) { |
| default: |
| return false; |
| |
| // Simply pass-thru the incoming address. |
| case SILInstructionKind::MarkUninitializedInst: |
| case SILInstructionKind::UncheckedAddrCastInst: |
| case SILInstructionKind::MarkDependenceInst: |
| // Look through a project_box to identify the underlying alloc_box as the |
| // accesed object. It must be possible to reach either the alloc_box or the |
| // containing enum in this loop, only looking through simple value |
| // propagation such as copy_value and begin_borrow. |
| case SILInstructionKind::ProjectBoxInst: |
| case SILInstructionKind::ProjectBlockStorageInst: |
| case SILInstructionKind::CopyValueInst: |
| case SILInstructionKind::BeginBorrowInst: |
| // Casting to RawPointer does not affect the AccessPath. When converting |
| // between address types, they must be layout compatible (with truncation). |
| case SILInstructionKind::AddressToPointerInst: |
| // Access to a Builtin.RawPointer. It may be important to continue looking |
| // through this because some RawPointers originate from identified |
| // locations. See the special case for global addressors, which return |
| // RawPointer, above. |
| // |
| // If the inductive search does not find a valid addressor, it will |
| // eventually reach the default case that returns in invalid location. This |
| // is correct for RawPointer because, although accessing a RawPointer is |
| // legal SIL, there is no way to guarantee that it doesn't access class or |
| // global storage, so returning a valid unidentified storage object would be |
| // incorrect. It is the caller's responsibility to know that formal access |
| // to such a location can be safely ignored. |
| // |
| // For example: |
| // |
| // - KeyPath Builtins access RawPointer. However, the caller can check |
| // that the access `isFromBuilin` and ignore the storage. |
| // |
| // - lldb generates RawPointer access for debugger variables, but SILGen |
| // marks debug VarDecl access as 'Unsafe' and SIL passes don't need the |
| // AccessedStorage for 'Unsafe' access. |
| case SILInstructionKind::PointerToAddressInst: |
| return true; |
| } |
| } |
| |
| /// Abstract CRTP class for a visiting instructions that are part of the use-def |
| /// chain from an accessed address up to the storage base. |
| /// |
| /// Given the address of a memory operation begin visiting at |
| /// getAccessedAddress(address). |
| template <typename Impl, typename Result = void> |
| class AccessUseDefChainVisitor { |
| protected: |
| Impl &asImpl() { return static_cast<Impl &>(*this); } |
| |
| public: |
| Result visitClassAccess(RefElementAddrInst *field) { |
| return asImpl().visitBase(field, AccessedStorage::Class); |
| } |
| Result visitTailAccess(RefTailAddrInst *tail) { |
| return asImpl().visitBase(tail, AccessedStorage::Tail); |
| } |
| Result visitArgumentAccess(SILFunctionArgument *arg) { |
| return asImpl().visitBase(arg, AccessedStorage::Argument); |
| } |
| Result visitBoxAccess(AllocBoxInst *box) { |
| return asImpl().visitBase(box, AccessedStorage::Box); |
| } |
| /// \p global may be either a GlobalAddrInst or the ApplyInst for a global |
| /// accessor function. |
| Result visitGlobalAccess(SILValue global) { |
| return asImpl().visitBase(global, AccessedStorage::Global); |
| } |
| Result visitYieldAccess(BeginApplyResult *yield) { |
| return asImpl().visitBase(yield, AccessedStorage::Yield); |
| } |
| Result visitStackAccess(AllocStackInst *stack) { |
| return asImpl().visitBase(stack, AccessedStorage::Stack); |
| } |
| Result visitNestedAccess(BeginAccessInst *access) { |
| return asImpl().visitBase(access, AccessedStorage::Nested); |
| } |
| Result visitUnidentified(SILValue base) { |
| return asImpl().visitBase(base, AccessedStorage::Unidentified); |
| } |
| |
| // Subclasses must provide implementations for: |
| // |
| // Result visitBase(SILValue base, AccessedStorage::Kind kind); |
| // Result visitNonAccess(SILValue base); |
| // Result visitPhi(SILPhiArgument *phi); |
| // Result visitStorageCast(SingleValueInstruction *cast, Operand *sourceOper); |
| // Result visitAccessProjection(SingleValueInstruction *projectedAddr, |
| // Operand *sourceOper); |
| |
| Result visit(SILValue sourceAddr); |
| }; |
| |
| template<typename Impl, typename Result> |
| Result AccessUseDefChainVisitor<Impl, Result>::visit(SILValue sourceAddr) { |
| if (auto *svi = dyn_cast<SingleValueInstruction>(sourceAddr)) { |
| if (auto *projOper = getAccessProjectionOperand(svi)) |
| return asImpl().visitAccessProjection(svi, projOper); |
| |
| if (isAccessedStorageCast(svi)) |
| return asImpl().visitStorageCast(svi, &svi->getAllOperands()[0]); |
| } |
| switch (sourceAddr->getKind()) { |
| default: |
| break; |
| |
| // MARK: Handle immediately-identifiable instructions. |
| |
| // An AllocBox is a fully identified memory location. |
| case ValueKind::AllocBoxInst: |
| return asImpl().visitBoxAccess(cast<AllocBoxInst>(sourceAddr)); |
| |
| // An AllocStack is a fully identified memory location, which may occur |
| // after inlining code already subjected to stack promotion. |
| case ValueKind::AllocStackInst: |
| return asImpl().visitStackAccess(cast<AllocStackInst>(sourceAddr)); |
| |
| case ValueKind::GlobalAddrInst: |
| return asImpl().visitGlobalAccess(sourceAddr); |
| |
| case ValueKind::ApplyInst: { |
| FullApplySite apply(cast<ApplyInst>(sourceAddr)); |
| if (auto *funcRef = apply.getReferencedFunctionOrNull()) { |
| if (getVariableOfGlobalInit(funcRef)) { |
| return asImpl().visitGlobalAccess(sourceAddr); |
| } |
| } |
| if (isExternalGlobalAddressor(cast<ApplyInst>(sourceAddr))) |
| return asImpl().visitUnidentified(sourceAddr); |
| |
| // Don't currently allow any other calls to return an accessed address. |
| return asImpl().visitNonAccess(sourceAddr); |
| } |
| case ValueKind::RefElementAddrInst: |
| return asImpl().visitClassAccess(cast<RefElementAddrInst>(sourceAddr)); |
| |
| // ref_tail_addr project an address from a reference. |
| // This is a valid address producer for nested @inout argument |
| // access, but it is never used for formal access of identified objects. |
| case ValueKind::RefTailAddrInst: |
| return asImpl().visitTailAccess(cast<RefTailAddrInst>(sourceAddr)); |
| |
| // A yield is effectively a nested access, enforced independently in |
| // the caller and callee. |
| case ValueKind::BeginApplyResult: |
| return asImpl().visitYieldAccess(cast<BeginApplyResult>(sourceAddr)); |
| |
| // A function argument is effectively a nested access, enforced |
| // independently in the caller and callee. |
| case ValueKind::SILFunctionArgument: |
| return asImpl().visitArgumentAccess( |
| cast<SILFunctionArgument>(sourceAddr)); |
| |
| // View the outer begin_access as a separate location because nested |
| // accesses do not conflict with each other. |
| case ValueKind::BeginAccessInst: |
| return asImpl().visitNestedAccess(cast<BeginAccessInst>(sourceAddr)); |
| |
| // Static index_addr is handled by getAccessProjectionOperand. Dynamic |
| // index_addr is currently unidentified because we can't form an AccessPath |
| // including them. |
| case ValueKind::SILUndef: |
| return asImpl().visitUnidentified(sourceAddr); |
| |
| // MARK: The sourceAddr producer cannot immediately be classified, |
| // follow the use-def chain. |
| |
| case ValueKind::StructExtractInst: |
| // Handle nested access to a KeyPath projection. The projection itself |
| // uses a Builtin. However, the returned UnsafeMutablePointer may be |
| // converted to an address and accessed via an inout argument. |
| if (isUnsafePointerExtraction(cast<StructExtractInst>(sourceAddr))) |
| return asImpl().visitUnidentified(sourceAddr); |
| return asImpl().visitNonAccess(sourceAddr); |
| |
| case ValueKind::SILPhiArgument: { |
| auto *phiArg = cast<SILPhiArgument>(sourceAddr); |
| if (phiArg->isPhiArgument()) { |
| return asImpl().visitPhi(phiArg); |
| } |
| |
| // A non-phi block argument may be a box value projected out of |
| // switch_enum. Address-type block arguments are not allowed. |
| if (sourceAddr->getType().isAddress()) |
| return asImpl().visitNonAccess(sourceAddr); |
| |
| checkSwitchEnumBlockArg(cast<SILPhiArgument>(sourceAddr)); |
| return asImpl().visitUnidentified(sourceAddr); |
| } |
| } // end switch |
| if (isAddressForLocalInitOnly(sourceAddr)) |
| return asImpl().visitUnidentified(sourceAddr); |
| |
| return asImpl().visitNonAccess(sourceAddr); |
| } |
| |
| } // end namespace swift |
| |
| //===----------------------------------------------------------------------===// |
| // AccessUseDefChainCloner |
| //===----------------------------------------------------------------------===// |
| |
| namespace swift { |
| |
| /// Clone all projections and casts on the access use-def chain until either the |
| /// specified predicate is true or the access base is reached. |
| /// |
| /// This will not clone ref_element_addr or ref_tail_addr because those aren't |
| /// part of the access chain. |
| template <typename UnaryPredicate> |
| class AccessUseDefChainCloner |
| : public AccessUseDefChainVisitor<AccessUseDefChainCloner<UnaryPredicate>, |
| SILValue> { |
| UnaryPredicate predicate; |
| SILInstruction *insertionPoint; |
| |
| public: |
| AccessUseDefChainCloner(UnaryPredicate predicate, |
| SILInstruction *insertionPoint) |
| : predicate(predicate), insertionPoint(insertionPoint) {} |
| |
| // Recursive main entry point |
| SILValue cloneUseDefChain(SILValue addr) { |
| if (!predicate(addr)) |
| return addr; |
| |
| return this->visit(addr); |
| } |
| |
| // Recursively clone an address on the use-def chain. |
| SingleValueInstruction *cloneProjection(SingleValueInstruction *projectedAddr, |
| Operand *sourceOper) { |
| SILValue projectedSource = cloneUseDefChain(sourceOper->get()); |
| SILInstruction *clone = projectedAddr->clone(insertionPoint); |
| clone->setOperand(sourceOper->getOperandNumber(), projectedSource); |
| return cast<SingleValueInstruction>(clone); |
| } |
| |
| // MARK: Visitor implementation |
| |
| SILValue visitBase(SILValue base, AccessedStorage::Kind kind) { |
| assert(false && "access base cannot be cloned"); |
| return SILValue(); |
| } |
| |
| SILValue visitNonAccess(SILValue base) { |
| assert(false && "unknown address root cannot be cloned"); |
| return SILValue(); |
| } |
| |
| SILValue visitPhi(SILPhiArgument *phi) { |
| assert(false && "unexpected phi on access path"); |
| return SILValue(); |
| } |
| |
| SILValue visitStorageCast(SingleValueInstruction *cast, Operand *sourceOper) { |
| return cloneProjection(cast, sourceOper); |
| } |
| |
| SILValue visitAccessProjection(SingleValueInstruction *projectedAddr, |
| Operand *sourceOper) { |
| return cloneProjection(projectedAddr, sourceOper); |
| } |
| }; |
| |
| template <typename UnaryPredicate> |
| SILValue cloneUseDefChain(SILValue addr, SILInstruction *insertionPoint, |
| UnaryPredicate shouldFollowUse) { |
| return AccessUseDefChainCloner<UnaryPredicate>(shouldFollowUse, |
| insertionPoint) |
| .cloneUseDefChain(addr); |
| } |
| |
| } // end namespace swift |
| |
| //===----------------------------------------------------------------------===// |
| // MARK: Verification |
| //===----------------------------------------------------------------------===// |
| |
| namespace swift { |
| |
| /// 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); |
| |
| } // end namespace swift |
| |
| #endif |