Files
swift-mirror/include/swift/SIL/MemAccessUtils.h
Nate Chandler 33d5c8648b [MemAccessUtils] Add to strip access storage casts
The new function stripAccessAndAccessStorageCasts is analogous to the
existing function stripAccessAndIdentityCasts but differs in that the
latter uses isAccessStorageIdentityCast whereas the new function uses
isAccessStorageCast.
2025-06-03 15:32:26 -07:00

2021 lines
79 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 - 2021 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
/// SILMemoryAccess.md for high-level design.
///
/// Terminology: In the examples below, 'address' is the address of the memory
/// operation, 'access' is the address of the formal access scope, 'base' is the
/// address of the formally accessed memory, and 'root' is the reference root of
/// the object that the formally accessed memory is a member of.
///
/// struct S {
/// var field: Int64
/// }
/// class C {
/// var prop: S
/// }
///
/// %root = alloc_ref $C
/// %base = ref_element_addr %root : $C, #C.prop
/// %access = begin_access [read] [static] %base : $*S
/// %address = struct_element_addr %access : $*S, #.field
/// %value = load [trivial] %address : $*Int64
/// end_access %access : $*S
///
/// OR
///
/// %root = alloc_box $S
/// %base = project_box %root : ${ var S }
/// %access = begin_access [read] [static] %base : $*S
/// %address = struct_element_addr %access : $*S, #.field
/// %value = load [trivial] %address : $*Int64
/// end_access %access : $*S
///
/// 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
/// (TODO: make storage casts within access scopes illegal). 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 or pointer type, but never a reference or box type.
/// Each object's property or its tail storage is separately accessed.
///
/// In addition to the free-standing functions, the AccessBase and
/// AccessStorage classes encapsulate the identity of an access. They can be
/// used to:
/// - directly compare and hash access identities
/// - exhaustively switch over the kinds of accesses
/// - cache access lookups for repeated queries
///
/// AccessBase::compute() follows the same logic as getAccessBase(), but if the
/// base is not recognized as a valid access, it returns invalid
/// AccessBase. AccessStorage::compute() extends this API by using
/// findReferenceRoot() to more precisely identify the storage object.
///
/// AccessBase::compute() and AccessStorage::compute() can be called on the
/// address of any memory operation, the address of a begin_access, or any other
/// address value. If the address is the operand of any enforced begin_access or
/// any memory operation that corresponds to formal access, then compute()
/// must return a valid AccessBase or AccessStorage 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 AccessStorage 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
/// AccessStorage::compute() simply bailed out on an unrecognized memory
/// address by returning an invalid AccessStorage, then the optimization could
/// make incorrect assumptions about the absence of access to globals or class
/// properties.
///
/// computeInScope() returns an AccessBase or AccessStorage 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 AccessStorage::computeInScope(), but it
/// should only be passed finds an address that is the operand of a begin_access
/// marker, rather than any arbitrary address. This must return a valid
/// AccessStorage 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 AccessStorage 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 SILMemoryAccess.md and the
/// class comments below for details. AccessPath::compute() and
/// AccessPath::computeInScope() mirror the AccessStorage API.
/// AccessPath::contains() and AccessPath::mayOverlap() provide efficient
/// comparison of access paths.
///
/// AccessPath::collectUses() gathers all reachable uses of the accessed
/// storage, allowing the selection of Exact, Inner, or Overlapping uses.
/// visitAccessStorageUses() 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 AccessStorage
/// 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);
/// Find the root of a reference, which may be a non-trivial type, box type, or
/// BridgeObject. This is guaranteed to be consistent with
/// AccessStorage::getRoot() and AccessPath::getRoot().
SILValue findReferenceRoot(SILValue ref);
/// Find the first owned root of the reference.
SILValue findOwnershipReferenceRoot(SILValue ref);
/// Look through all ownership forwarding instructions to find the values which
/// were originally borrowed.
///
/// Note: This treats guaranteed forwarding phis like roots even though they do
/// not introduce the borrow scope. This ensures that all roots dominate \p
/// reference Value. But the client will need to handle forwarding phis.
void findGuaranteedReferenceRoots(SILValue referenceValue,
bool lookThroughNestedBorrows,
SmallVectorImpl<SILValue> &roots);
/// Find the aggregate containing the first owned root of the
/// reference. Identical to findOwnershipReferenceRoot, but looks through
/// struct_extract, tuple_extract, etc.
SILValue findOwnershipReferenceAggregate(SILValue ref);
/// 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);
}
/// Whether \p instruction accesses storage whose representation is either (1)
/// unidentified such as by reading a pointer or (2) global.
bool mayAccessPointer(SILInstruction *instruction);
/// Whether this instruction loads or copies a value whose storage does not
/// increment the stored value's reference count.
bool mayLoadWeakOrUnowned(SILInstruction* instruction);
/// Conservatively, whether this instruction could involve a synchronization
/// point like a memory barrier, lock or syscall.
bool maySynchronize(SILInstruction* instruction);
/// Conservatively, whether this instruction could be a barrier to hoisting
/// destroys.
///
/// Does not consider function so effects, so every apply is treated as a
/// barrier.
bool mayBeDeinitBarrierNotConsideringSideEffects(SILInstruction *instruction);
} // end namespace swift
//===----------------------------------------------------------------------===//
// MARK: AccessStorage
//===----------------------------------------------------------------------===//
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 };
/// When walking from a value to its storage, casts may be encountered. The
/// cases describe variety of encountered casts, categorized by the kind of
/// transformation that the casts perform.
///
/// The enum values are ordered. Each successive cast kind is more
/// transformative than the last.
///
/// TODO: Distinguish between LayoutEquivalent and LayoutCompatible.
enum class AccessStorageCast { Identity, Type };
/// The physical representation used to identify access information and common
/// API used by both AccessBase and AccessStorage.
///
/// 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.
///
/// Unidentified access may have a valid AccessRepresentation. This is the case
/// for a SILValue that produces the 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. A
/// valid Unidentified address cannot represent any arbitrary address--it must
/// at least be proven not to correspond to any class or global variable
/// access. The Unidentified address can be nested within another access to the
/// same object such that the Unidentified value is derived from the identified
/// Class/Global access. But the Unidentified access can never be the *only*
/// formal access to Class/Global memory. This would break assumptions that all
/// Class/Global accesses are discoverable.
///
/// An *invalid* AccessStorage 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
/// AccessStorage is either valid or explicitly guarded by an "unsafe"
/// begin_access.
///
/// Some identified storage is also known to be "uniquely identified". Memory
/// operations on "uniquely identified" storage cannot overlap with any other
/// memory operation on distinct "uniquely identified" storage.
class AccessRepresentation {
public:
/// Enumerate over all valid begin_access bases. Clients can use a covered
/// switch to warn if AccessStorage ever adds a case.
enum Kind : uint8_t {
Box,
Stack,
Global,
Class,
Tail,
Argument, // Address or RawPointer argument
Yield,
Nested,
Unidentified,
NumKindBits = countBitsUsed(static_cast<unsigned>(Unidentified))
};
static const char *getKindName(Kind k);
// 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;
// Give object tail storage a fake large property index for convenience.
static constexpr unsigned TailIndex = std::numeric_limits<int>::max();
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.
//
// AccessStorageAnalysis 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(AccessRepresentation, 32,
kind : ReservedKindBits,
isLet : 1,
elementIndex : 32 - (ReservedKindBits + 1));
// Define bits for use in AccessStorageAnalysis. Each identified storage
// object is mapped to one instance of this subclass.
SWIFT_INLINE_BITFIELD_FULL(StorageAccessInfo, AccessRepresentation,
64 - NumAccessRepresentationBits,
accessKind : NumSILAccessKindBits,
noNestedConflict : 1,
storageIndex : 64 - (NumAccessRepresentationBits
+ 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 remaining bits are sufficient to index all
// begin_[unpaired_]access instructions.
//
// `AccessRepresentation` refers to the AccessRepresentationBitfield defined
// above, setting aside enough bits for common data.
SWIFT_INLINE_BITFIELD_FULL(AccessEnforcementOptsInfo,
AccessRepresentation,
64 - NumAccessRepresentationBits,
seenNestedConflict : 1,
seenIdenticalStorage : 1,
beginAccessIndex :
62 - NumAccessRepresentationBits);
// 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(DomAccessStorage, AccessRepresentation, 1 + 1,
isInner : 1, containsRead : 1);
} Bits;
// 'value' or 'global' and 'isLet' are initialized in the subclass.
union {
// For AccessBase, 'value' always contains the base address.
//
// For AccessStorage:
// - For Global, 'global' refers to the global variable
// - For Class and Tail, 'value' contains the object root
// - For other access kinds, 'value' contains the base address
SILValue value;
SILGlobalVariable *global;
};
void setLetAccess(bool isLet) {
Bits.AccessRepresentation.isLet = isLet;
}
public:
AccessRepresentation() : value() {
Bits.opaqueBits = 0;
initKind(Unidentified, InvalidElementIndex);
}
AccessRepresentation(SILValue base, Kind kind);
Kind getKind() const {
return static_cast<Kind>(Bits.AccessRepresentation.kind);
}
void initKind(Kind k, unsigned elementIndex) {
Bits.opaqueBits = 0;
Bits.AccessRepresentation.kind = k;
Bits.AccessRepresentation.elementIndex = elementIndex;
}
unsigned getElementIndex() const {
return Bits.AccessRepresentation.elementIndex;
}
void setElementIndex(unsigned elementIndex) {
Bits.AccessRepresentation.elementIndex = elementIndex;
}
// Return true if this is a valid access representation.
operator bool() const { return getKind() != Unidentified || value; }
// Clear any bits reserved for subclass data. Useful for up-casting back to
// the base class.
void resetSubclassData() {
initKind(getKind(), Bits.AccessRepresentation.elementIndex);
}
SILValue getValue() const {
assert(getKind() != Global && getKind() != Class && getKind() != Tail);
assert(value && "Invalid storage has an invalid value");
return value;
}
unsigned getParamIndex() const {
assert(getKind() == Argument);
return getElementIndex();
}
SILFunctionArgument *getArgument() const {
assert(getKind() == Argument);
return cast<SILFunctionArgument>(value);
}
/// Return true if this access is based on a reference-counted object.
bool isReference() const {
return getKind() == Box || getKind() == Class || getKind() == Tail;
}
/// Return true if the given access is guaranteed to be within an object of
/// class type.
bool isObjectAccess() const {
return getKind() == Class || getKind() == Tail;
}
unsigned getPropertyIndex() const {
assert(getKind() == Class);
return getElementIndex();
}
/// Return true if the given storage objects have identical access information
///
/// This compares only the AccessStorage 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 hasIdenticalAccessInfo(const AccessRepresentation &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:
return isa<AllocBoxInst>(value);
case Stack:
return true;
case Global:
case Class:
case Tail:
case Argument:
case Yield:
case Nested:
case Unidentified:
return false;
}
llvm_unreachable("unhandled kind");
}
/// 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:
return isa<AllocBoxInst>(value);
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 AccessRepresentation &other) const;
/// 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;
}
/// Return true if the given access is on a 'let' lvalue.
bool isLetAccess() const { return Bits.AccessRepresentation.isLet; }
void print(raw_ostream &os) const;
private:
// Disable direct comparison because we allow subclassing with bitfields.
// Currently, we use DenseMapInfo to unique storage, which defines key
// equality only in terms of the base AccessStorage class bits.
bool operator==(const AccessRepresentation &) const = delete;
bool operator!=(const AccessRepresentation &) const = delete;
};
/// The base of a formal access.
///
/// Note that the SILValue that represents a storage object is not
/// necessarily 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.
class AccessBase : public AccessRepresentation {
public:
/// Return an AccessBase for the formally accessed variable pointed to by \p
/// sourceAddress.
///
/// \p sourceAddress may be an address type or Builtin.RawPointer.
///
/// If \p sourceAddress is within a formal access scope, which does not have
/// "Unsafe" enforcement, then this always returns the valid base.
///
/// If \p sourceAddress is not within a formal access scope, or within an
/// "Unsafe" scope, then this finds the formal base if possible,
/// otherwise returning an invalid base.
static AccessBase compute(SILValue sourceAddress);
// Do not add any members to this class. AccessBase can be copied as
// AccessRepresentation.
public:
AccessBase() = default;
AccessBase(SILValue base, Kind kind);
/// Return the base address of this access.
///
/// Precondition: this is a valid AccessedBase.
///
/// Postcondition: the returned value has address or RawPointer type.
SILValue getBaseAddress() const {
assert(value && "An invalid base value implies invalid storage");
assert(value->getType().isAddress()
|| isa<BuiltinRawPointerType>(value->getType().getASTType()));
return value;
}
/// Return the immediate reference for the box or class object being accessed.
///
/// Use findReferenceRoot() or findOwnershipRoot() on this result to precisely
/// identify the storage object.
///
/// Precondition: isReference() is true.
SILValue getReference() const;
/// Return the OSSA root of the reference being accessed.
///
/// Precondition: isReference() is true.
SILValue getOwnershipReferenceRoot() const {
return findOwnershipReferenceRoot(getReference());
}
/// Return the OSSA root of the reference being accessed
/// looking through struct_extract, tuple_extract, etc.
/// Precondition: isReference() is true.
SILValue getOwnershipReferenceAggregate() const {
return findOwnershipReferenceAggregate(getReference());
}
/// Return the storage root of the reference being accessed.
///
/// Precondition: isReference() is true.
SILValue getStorageReferenceRoot() const {
return findReferenceRoot(getReference());
}
/// Return the global variable being accessed. Always valid.
///
/// Precondition: getKind() == Global
SILGlobalVariable *getGlobal() const;
/// Returns the ValueDecl for the formal access, if it can be
/// determined. Otherwise returns null.
const ValueDecl *getDecl() const;
/// Return true if this base address may be derived from a reference that is
/// only valid within a locally scoped OSSA lifetime. This is not true for
/// scoped storage such as alloc_stack and @in argument. It can be
/// independently assumed that addresses are only used within the scope of the
/// storage object.
///
/// Useful to determine whether addresses with the same AccessStorage are in
/// fact substitutable without fixing OSSA lifetime.
bool hasLocalOwnershipLifetime() const;
void print(raw_ostream &os) const;
void dump() const;
};
/// Represents the identity of a storage object being accessed.
///
/// Combines AccessBase with the reference root for Class and Tail access to
/// more precisely identify storage. For efficiency of the physical
/// representation, this does not preserve the base address. For convenient
/// access to both the address base and storage use AccessStorageWithBase.
///
/// 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.
///
/// Support for integer IDs and bitsets. An AccessStorage value has enough
/// extra bits to store a unique index for each identified access in a
/// function. An AccessStorage (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 AccessStorage : public AccessRepresentation {
public:
/// Return an AccessStorage 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 type or Builtin.RawPointer.
///
/// 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 AccessStorage compute(SILValue sourceAddress);
/// Return an AccessStorage object that identifies formal access scope that
/// immediately encloses \p sourceAddress.
///
/// \p sourceAddress may be an address type or Builtin.RawPointer.
///
/// 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 AccessStorage computeInScope(SILValue sourceAddress);
/// Create storage for the tail elements of \p object.
static AccessStorage forBase(AccessBase base) {
return AccessStorage(base.getBaseAddress(), base.getKind());
}
/// Create storage for the tail elements of \p object.
static AccessStorage forObjectTail(SILValue object);
// Do not add any members to this class. AccessBase can be copied as
// AccessRepresentation.
public:
AccessStorage() = default;
AccessStorage(SILValue base, Kind kind);
/// Return a new AccessStorage for Class/Tail/Box access based on
/// existing storage and a new object.
AccessStorage transformReference(SILValue object) const {
AccessStorage storage;
storage.initKind(getKind(), getElementIndex());
storage.value = findReferenceRoot(object);
return storage;
}
SILGlobalVariable *getGlobal() const {
assert(getKind() == Global);
return global;
}
SILValue getObject() const {
assert(isReference());
return value;
}
/// 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 AccessStorage::Box:
case AccessStorage::Stack:
case AccessStorage::Nested:
case AccessStorage::Argument:
case AccessStorage::Yield:
case AccessStorage::Unidentified:
return getValue();
case AccessStorage::Global:
return SILValue();
case AccessStorage::Class:
case AccessStorage::Tail:
return getObject();
}
llvm_unreachable("covered switch");
}
/// 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 AccessStorage 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 hasIdenticalStorage(const AccessStorage &other) const {
return hasIdenticalAccessInfo(other);
}
/// Returns the ValueDecl for the underlying storage, if it can be
/// determined. Otherwise returns null.
const ValueDecl *getDecl() const;
/// Get all leaf uses of all address, pointer, or box values that have a this
/// AccessStorage 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
/// 'visitAccessStorageUses' 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;
};
} // end namespace swift
namespace llvm {
/// Enable using AccessStorage as a key in DenseMap.
/// Do *not* include any extra pass data in key equality.
///
/// AccessStorage 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::AccessStorage> {
static swift::AccessStorage getEmptyKey() {
return swift::AccessStorage(swift::SILValue::getFromOpaqueValue(
llvm::DenseMapInfo<void *>::getEmptyKey()),
swift::AccessStorage::Unidentified);
}
static swift::AccessStorage getTombstoneKey() {
return swift::AccessStorage(swift::SILValue::getFromOpaqueValue(
llvm::DenseMapInfo<void *>::getTombstoneKey()),
swift::AccessStorage::Unidentified);
}
static unsigned getHashValue(swift::AccessStorage storage) {
switch (storage.getKind()) {
case swift::AccessStorage::Unidentified:
if (!storage)
return DenseMapInfo<swift::SILValue>::getHashValue(swift::SILValue());
LLVM_FALLTHROUGH;
case swift::AccessStorage::Box:
case swift::AccessStorage::Stack:
case swift::AccessStorage::Nested:
case swift::AccessStorage::Yield:
return DenseMapInfo<swift::SILValue>::getHashValue(storage.getValue());
case swift::AccessStorage::Argument:
return storage.getParamIndex();
case swift::AccessStorage::Global:
return DenseMapInfo<void *>::getHashValue(storage.getGlobal());
case swift::AccessStorage::Class:
return llvm::hash_combine(storage.getObject(),
storage.getPropertyIndex());
case swift::AccessStorage::Tail:
return DenseMapInfo<swift::SILValue>::getHashValue(storage.getObject());
}
llvm_unreachable("Unhandled AccessStorageKind");
}
static bool isEqual(swift::AccessStorage LHS, swift::AccessStorage RHS) {
return LHS.hasIdenticalStorage(RHS);
}
};
} // namespace llvm
namespace swift {
/// For convenience, encapsulate and AccessStorage value along with its
/// accessed base address.
struct AccessStorageWithBase {
/// Identical to AccessStorage::computeInScope but walks through begin_access.
static AccessStorageWithBase compute(SILValue sourceAddress);
/// Identical to AccessStorage::compute but stops at begin_access
static AccessStorageWithBase computeInScope(SILValue sourceAddress);
AccessStorage 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;
AccessStorageWithBase(AccessStorage storage, SILValue base)
: storage(storage), base(base) {}
AccessBase getAccessBase() const {
return AccessBase(base, storage.getKind());
}
/// Returns the ValueDecl for the underlying storage, if it can be
/// determined. Otherwise returns null. This is more complete than either
/// AccessBase::getDecl() or AccessStorage::getDecl().
const ValueDecl *getDecl() const;
bool operator==(const AccessStorageWithBase &other) const {
return storage.hasIdenticalStorage(other.storage) && base == other.base;
}
bool operator!=(const AccessStorageWithBase &other) const {
return !(*this == other);
}
void print(raw_ostream &os) const;
void dump() const;
};
/// Extends AccessStorageWithBase by adding information that was obtained while
/// visiting from a particular address, to which an instance of this is
/// relative.
struct RelativeAccessStorageWithBase {
/// Identical to AccessStorageWithBase::compute but preserves information
/// specific to the walk from address;
static RelativeAccessStorageWithBase compute(SILValue address);
/// Identical to AccessStorageWithBase::computeInScope but preserves
/// information specific to the walk from address;
static RelativeAccessStorageWithBase computeInScope(SILValue address);
/// The address to which this RelativeAccessStorageWithBase is relative.
SILValue address;
/// The underlying access storage and base.
AccessStorageWithBase storageWithBase;
/// The most transformative cast that was seen between when walking from
/// address to storage.base;
std::optional<AccessStorageCast> cast;
AccessStorage getStorage() const { return storageWithBase.storage; }
};
/// Return an AccessStorage 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 AccessStorage identifyFormalAccess(BeginAccessInst *beginAccess) {
return AccessStorage::computeInScope(beginAccess->getSource());
}
inline AccessStorage
identifyFormalAccess(BeginUnpairedAccessInst *beginAccess) {
return AccessStorage::computeInScope(beginAccess->getSource());
}
} // end namespace swift
//===----------------------------------------------------------------------===//
// MARK: AccessPath
//===----------------------------------------------------------------------===//
namespace swift {
/// Identify an addressable location based the AccessStorage 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 beginning 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 AccessStorage::compute().
///
/// Storing an AccessPath amortizes 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 AccessStorage.
///
/// 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);
/// Creates an AccessPass, which identifies the first tail-element of the
/// object \p rootReference.
static AccessPath forTailStorage(SILValue rootReference);
// 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
// - AccessStorage::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 isPrefixOf(PathNode other) { return node->isPrefixOf(other.node); }
bool operator==(PathNode other) const { return node == other.node; }
bool operator!=(PathNode other) const { return node != other.node; }
};
private:
AccessStorage 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).
//
// AccessStorage is only used to identify the storage location; AccessPath
// ignores its subclass bits.
AccessPath(AccessStorage storage, PathNode pathNode, int offset)
: storage(storage), pathNode(pathNode), offset(offset) {
assert(pathNode.isValid() || !storage && "Access path requires a pathNode");
}
AccessPath() = default;
bool operator==(AccessPath other) const {
return
storage.hasIdenticalStorage(other.storage)
&& pathNode == other.pathNode
&& offset == other.offset;
}
bool operator!=(AccessPath other) const { return !(*this == other); }
bool isValid() const { return pathNode.isValid(); }
AccessStorage 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
/// AccessStorage 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;
/// Returns a new AccessPass, identical to this AccessPath, except that the
/// offset is replaced with \p newOffset.
AccessPath withOffset(int newOffset) const {
return AccessPath(storage, pathNode, newOffset);
}
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
// identify 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 box storage, the project_box; for
// global storage the global_addr or initializer apply. For other
// storage, it is the same as accessPath.getRoot().
//
// Note: base may be invalid for phi patterns, even though the accessPath is
// valid because we don't currently keep track of multiple bases. Multiple
// bases for the same storage can happen with global_addr, ref_element_addr,
// ref_tail_addr, and project_box.
//
// FIXME: add a structural requirement to SIL/OSSA so valid storage has
// a single base. For most cases, it is as simple by sinking the
// projection. For index_addr, it may require hoisting ref_tail_addr.
SILValue base;
/// Compute the access path at \p address, and record the access base. This
/// ignores begin_access markers, returning the outermost AccessStorage.
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) {}
AccessBase getAccessBase() const {
return AccessBase(base, accessPath.getStorage().getKind());
}
bool isValid() const { return base && accessPath.isValid(); }
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;
};
// Visits all the "product leaves" of the type tree of the specified value and
// invokes provided visitor, identifying the leaf by its path node and
// providing its type.
//
// The "product leaves" are the leaves obtained by only looking through type
// products (structs and tuples) and NOT type sums (enums).
//
// Returns false if the access path couldn't be computed.
bool visitProductLeafAccessPathNodes(
SILValue address, TypeExpansionContext tec, SILFunction &function,
std::function<void(AccessPath::PathNode, SILType)> visitor);
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::AccessStorage>::getEmptyKey(),
swift::AccessPath::PathNode(
DenseMapInfo<swift::IndexTrieNode *>::getEmptyKey()), 0);
}
static inline swift::AccessPath getTombstoneKey() {
return swift::AccessPath(
DenseMapInfo<swift::AccessStorage>::getTombstoneKey(),
swift::AccessPath::PathNode(
DenseMapInfo<swift::IndexTrieNode *>::getTombstoneKey()), 0);
}
static inline unsigned getHashValue(const swift::AccessPath &val) {
return llvm::hash_combine(
DenseMapInfo<swift::AccessStorage>::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;
}
};
// Allow AccessPath::PathNode to be used as a pointer-like template argument.
template<>
struct PointerLikeTypeTraits<swift::AccessPath::PathNode> {
static inline void *getAsVoidPointer(swift::AccessPath::PathNode node) {
return (void *)node.node;
}
static inline swift::AccessPath::PathNode getFromVoidPointer(void *pointer) {
return swift::AccessPath::PathNode((swift::IndexTrieNode *)pointer);
}
enum { NumLowBitsAvailable =
PointerLikeTypeTraits<swift::IndexTrieNode *>::NumLowBitsAvailable };
};
} // 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 visitAccessStorageUses(AccessUseVisitor &visitor, AccessStorage 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);
/// Similar to visitAccessPathUses, but the visitor is restricted to a specific
/// access base, such as a particular ref_element_addr.
bool visitAccessPathBaseUses(AccessUseVisitor &visitor,
AccessPathWithBase accessPathWithBase,
SILFunction *function);
} // end namespace swift
//===----------------------------------------------------------------------===//
// MARK: UniqueAddressUses
//===----------------------------------------------------------------------===//
namespace swift {
/// Analyze and classify the leaf uses of unique storage.
///
/// Storage that has a unique set of roots within this function includes
/// alloc_stack, alloc_box, exclusive argument, and global variables. All access
/// to the storage within this function is derived from these roots.
///
/// Gather the kinds of uses that are typically relevant to algorithms:
/// - accesses (specifically, begin_access insts)
/// - loads (including copies out of, not including inout args)
/// - stores (including copies into and inout args)
/// - destroys (of the entire aggregate)
/// - debugUses (only populated when preserveDebugInfo == false)
/// - unknownUses (e.g. address_to_pointer, box escape)
struct UniqueStorageUseVisitor {
static bool findUses(UniqueStorageUseVisitor &visitor);
SILFunction *function;
AccessStorage storage;
UniqueStorageUseVisitor(AccessStorage storage, SILFunction *function)
: function(function), storage(storage) {}
virtual ~UniqueStorageUseVisitor() = default;
virtual bool visitBeginAccess(Operand *use) = 0;
virtual bool visitLoad(Operand *use) = 0;
virtual bool visitStore(Operand *use) = 0;
virtual bool visitDestroy(Operand *use) = 0;
virtual bool visitDealloc(Operand *use) = 0;
virtual bool visitDebugUse(Operand *use) = 0;
virtual bool visitUnknownUse(Operand *use) = 0;
};
} // 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 we can prove is:
///
/// 1. Only initialized once in its own def block.
/// 2. Never written to again except by destroy_addr.
///
/// Then we return the single initializing use and if \p destroyingUsers was
/// non-null, On return, if non-null, \p 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.
Operand *getSingleInitAllocStackUse(
AllocStackInst *asi, SmallVectorImpl<Operand *> *destroyingUses = nullptr);
/// Same as getSingleInitAllocStack except we throw away the single use and just
/// provide a bool.
inline bool isSingleInitAllocStack(AllocStackInst *asi,
SmallVectorImpl<Operand *> &destroyingUses) {
return getSingleInitAllocStackUse(asi, &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 AccessStorage 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 AccessStorage because it only makes sense to use
/// in SILGen before access markers are emitted, or when verifying access
/// markers.
bool isPossibleFormalAccessStorage(const AccessStorage &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 equivalence of
/// the reference at operand zero.
bool isIdentityPreservingRefCast(SingleValueInstruction *svi);
/// Return true if \p svi is a cast that preserves the identity equivalence and
/// reference-counting equivalence of the reference at operand zero.
bool isIdentityAndOwnershipPreservingRefCast(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 isAccessStorageCast() 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;
};
}
/// A cast for the purposes of AccessStorage which may change the
/// underlying type but doesn't affect the AccessPath. See isAccessStorageCast.
inline bool isAccessStorageTypeCast(SingleValueInstruction *svi) {
switch (svi->getKind()) {
default:
return false;
// This extracts out the block storage from an alloc_stack. We do not want
// to treat it as any more than a cast of the underlying value.
case SILInstructionKind::ProjectBlockStorageInst:
// Simply pass-thru the incoming address. But change its type!
case SILInstructionKind::MoveOnlyWrapperToCopyableAddrInst:
case SILInstructionKind::CopyableToMoveOnlyWrapperAddrInst:
case SILInstructionKind::MoveOnlyWrapperToCopyableBoxInst:
// Simply pass-thru the incoming address. But change its type!
case SILInstructionKind::UncheckedAddrCastInst:
// 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
// AccessStorage for 'Unsafe' access.
case SILInstructionKind::PointerToAddressInst:
return true;
}
}
/// A cast for the purposes of AccessStorage which doesn't change the
/// underlying type and doesn't affect the AccessPath. See isAccessStorageCast.
inline bool isAccessStorageIdentityCast(SingleValueInstruction *svi) {
switch (svi->getKind()) {
default:
return false;
// Simply pass-thru the incoming address.
case SILInstructionKind::MarkUninitializedInst:
case SILInstructionKind::MarkUnresolvedNonCopyableValueInst:
case SILInstructionKind::DropDeinitInst:
case SILInstructionKind::MarkUnresolvedReferenceBindingInst:
case SILInstructionKind::MarkDependenceInst:
case SILInstructionKind::CopyValueInst:
case SILInstructionKind::BeginBorrowInst:
return true;
}
}
// Strip access markers and casts that preserve the address type.
//
// Consider using RelativeAccessStorageWithBase::compute().
inline SILValue stripAccessAndIdentityCasts(SILValue v) {
if (auto *bai = dyn_cast<BeginAccessInst>(v)) {
return stripAccessAndIdentityCasts(bai->getOperand());
}
if (auto *svi = dyn_cast<SingleValueInstruction>(v)) {
if (isAccessStorageIdentityCast(svi)) {
return stripAccessAndIdentityCasts(svi->getAllOperands()[0].get());
}
}
return v;
}
/// 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 isAccessStorageCast(SingleValueInstruction *svi) {
return isAccessStorageTypeCast(svi) || isAccessStorageIdentityCast(svi);
}
// Strip access markers and casts that preserve the access storage.
//
// Compare to stripAccessAndIdentityCasts. This function strips cast that
// change the type.
inline SILValue stripAccessAndAccessStorageCasts(SILValue v) {
if (auto *bai = dyn_cast<BeginAccessInst>(v)) {
return stripAccessAndAccessStorageCasts(bai->getOperand());
}
if (auto *svi = dyn_cast<SingleValueInstruction>(v)) {
if (isAccessStorageCast(svi)) {
return stripAccessAndAccessStorageCasts(svi->getAllOperands()[0].get());
}
}
return v;
}
/// 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, AccessStorage::Class);
}
Result visitTailAccess(RefTailAddrInst *tail) {
return asImpl().visitBase(tail, AccessStorage::Tail);
}
Result visitArgumentAccess(SILFunctionArgument *arg) {
return asImpl().visitBase(arg, AccessStorage::Argument);
}
Result visitBoxAccess(ProjectBoxInst *box) {
return asImpl().visitBase(box, AccessStorage::Box);
}
/// \p global may be either a GlobalAddrInst or the ApplyInst for a global
/// accessor function.
Result visitGlobalAccess(SILValue global) {
return asImpl().visitBase(global, AccessStorage::Global);
}
Result visitYieldAccess(MultipleValueInstructionResult *yield) {
return asImpl().visitBase(yield, AccessStorage::Yield);
}
Result visitStackAccess(AllocStackInst *stack) {
return asImpl().visitBase(stack, AccessStorage::Stack);
}
Result visitNestedAccess(BeginAccessInst *access) {
return asImpl().visitBase(access, AccessStorage::Nested);
}
Result visitUnidentified(SILValue base) {
return asImpl().visitBase(base, AccessStorage::Unidentified);
}
// Subclasses must provide implementations for:
//
// Result visitBase(SILValue base, AccessStorage::Kind kind);
// Result visitNonAccess(SILValue base);
// Result visitPhi(SILPhiArgument *phi);
// Result visitStorageCast(SingleValueInstruction *cast, Operand *sourceOper,
// AccessStorageCast cast); 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 (isAccessStorageTypeCast(svi))
return asImpl().visitStorageCast(svi, &svi->getAllOperands()[0],
AccessStorageCast::Type);
if (isAccessStorageIdentityCast(svi))
return asImpl().visitStorageCast(svi, &svi->getAllOperands()[0],
AccessStorageCast::Identity);
if (auto *sbi = dyn_cast<StoreBorrowInst>(svi))
return asImpl().visitStorageCast(
svi, &sbi->getAllOperands()[CopyLikeInstruction::Dest],
AccessStorageCast::Identity);
}
switch (sourceAddr->getKind()) {
default:
break;
// MARK: Handle immediately-identifiable instructions.
case ValueKind::ProjectBoxInst:
return asImpl().visitBoxAccess(cast<ProjectBoxInst>(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::MultipleValueInstructionResult:
if (auto *baResult = isaResultOf<BeginApplyInst>(sourceAddr))
return asImpl().visitYieldAccess(baResult);
break;
// 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->isPhi()) {
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 the
/// checkBase predicate returns a valid base.
///
/// See comments on the cloneUseDefChain() API.
template <typename CheckBase>
class AccessUseDefChainCloner
: public AccessUseDefChainVisitor<AccessUseDefChainCloner<CheckBase>,
SILValue> {
CheckBase checkBase;
SILInstruction *insertionPoint;
SILValue placeHolder = SILValue();
public:
AccessUseDefChainCloner(CheckBase checkBase, SILInstruction *insertionPoint)
: checkBase(checkBase), insertionPoint(insertionPoint) {}
// Main entry point
SILValue cloneUseDefChain(SILValue addr) {
placeHolder = SILValue();
return cloneRecursive(addr);
}
// Secondary entry point to check that cloning will succeed.
bool canCloneUseDefChain(SILValue addr) {
// Use any valid address as a placeholder. It is inaccessible.
placeHolder = addr;
return cloneRecursive(addr);
}
SILValue cloneRecursive(SILValue addr) {
if (SILValue base = checkBase(addr))
return base;
return this->visit(addr);
}
// Recursively clone an address on the use-def chain.
//
// Helper for cloneUseDefChain.
SILValue cloneProjection(SingleValueInstruction *projectedAddr,
Operand *sourceOper) {
SILValue projectedSource = cloneRecursive(sourceOper->get());
if (!projectedSource)
return nullptr;
if (placeHolder)
return placeHolder;
SILInstruction *clone = projectedAddr->clone(insertionPoint);
clone->setOperand(sourceOper->getOperandNumber(), projectedSource);
return cast<SingleValueInstruction>(clone);
}
// MARK: Visitor implementation
SILValue visitBase(SILValue base, AccessStorage::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,
AccessStorageCast) {
// The cloner does not currently know how to create compensating
// end_borrows or fix mark_dependence operands.
if (isa<BeginBorrowInst>(cast) || isa<MarkDependenceInst>(cast))
return SILValue();
return cloneProjection(cast, sourceOper);
}
SILValue visitNestedAccess(BeginAccessInst *access) {
// The cloner does not currently know how to handle begin_access
return SILValue();
}
SILValue visitAccessProjection(SingleValueInstruction *projectedAddr,
Operand *sourceOper) {
return cloneProjection(projectedAddr, sourceOper);
}
};
/// Clone all projections and casts on the access use-def chain until the
/// checkBase predicate returns a valid base.
///
/// This will not clone ref_element_addr or ref_tail_addr because those aren't
/// part of the access chain.
///
/// CheckBase is a unary predicate that takes the next source address and either
/// returns a valid SILValue to use as the base of the cloned access path, or an
/// invalid SILValue to continue cloning.
///
/// CheckBase must return a valid SILValue either before attempting to clone the
/// access base. The most basic valid predicate is:
///
/// auto checkBase = [&](SILValue srcAddr) {
/// return (srcAddr == accessBase) ? srcAddr : SILValue();
/// }
template <typename CheckBase>
SILValue cloneUseDefChain(SILValue addr, SILInstruction *insertionPoint,
CheckBase checkBase) {
return AccessUseDefChainCloner<CheckBase>(checkBase, insertionPoint)
.cloneUseDefChain(addr);
}
/// Analog to cloneUseDefChain to check validity. begin_borrow and
/// mark_dependence currently cannot be cloned.
template <typename CheckBase>
bool canCloneUseDefChain(SILValue addr, CheckBase checkBase) {
return AccessUseDefChainCloner<CheckBase>(checkBase, nullptr)
.canCloneUseDefChain(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