mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Actor initializers have a flow-sensitive property where they are isolated to the actor being initialized only after the actor instance itself is fully-initialized. However, this behavior was not being reflected in the expansion of `#isolation`, which was always expanding to `self`, even before `self` is fully formed. This led to a source compatibility issue with code that used the async for..in loop within an actor initializer *prior* to the point where the actor was fully initialized, because the type checker is introducing the `#isolation` (SE-0421) but Definite Initialization properly rejects the use of `self` before it is initialized. Address this issue by delaying the expansion of `#isolation` until after the actor is fully initialized. In SILGen, we introduce a new builtin for this case (and *just* this case) called `flowSensitiveSelfIsolation`, which takes in `self` as its argument and produces an `(any Actor)?`. Definite initialization does not treat this as a use of `self`. Rather, it tracks these builtins and replaces them either with `self` (if it is fully-initialized at this point) or `nil` (if it is not fully-initialized at this point), mirroring the flow-sensitive isolation semantics described in SE-0327. Fixes rdar://127080037.
373 lines
13 KiB
C++
373 lines
13 KiB
C++
//===--- DIMemoryUseCollector.h -------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file declares logic used by definitive analysis related passes that look
|
|
// at all the instructions that access a memory object. This is quite specific
|
|
// to definitive analysis in that it is tuple element sensitive instead of
|
|
// relying on SROA.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SWIFT_SILOPTIMIZER_PASSMANAGER_DIMEMORYUSECOLLECTOR_H
|
|
#define SWIFT_SILOPTIMIZER_PASSMANAGER_DIMEMORYUSECOLLECTOR_H
|
|
|
|
#include "swift/Basic/LLVM.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/SILType.h"
|
|
#include "llvm/ADT/APInt.h"
|
|
#include "llvm/ADT/TinyPtrVector.h"
|
|
|
|
namespace swift {
|
|
|
|
class SILBuilder;
|
|
|
|
namespace ownership {
|
|
|
|
struct DIElementUseInfo;
|
|
|
|
/// This struct holds information about the memory object being analyzed that is
|
|
/// required to correctly break it down into elements.
|
|
///
|
|
/// This includes a collection of utilities for reasoning about (potentially
|
|
/// recursively) exploded aggregate elements, and computing access paths and
|
|
/// indexes into the flattened namespace.
|
|
///
|
|
/// The flattened namespace is assigned lexicographically. For example, in:
|
|
/// (Int, ((Float, (), Double)))
|
|
/// the Int member is numbered 0, the Float is numbered 1, and the Double is
|
|
/// numbered 2. Empty tuples don't get numbered since they contain no state.
|
|
///
|
|
/// Structs and classes have their elements exploded when we are analyzing the
|
|
/// 'self' member in an initializer for the aggregate.
|
|
///
|
|
/// Derived classes have an additional field at the end that models whether or
|
|
/// not super.init() has been called or not.
|
|
class DIMemoryObjectInfo {
|
|
/// The uninitialized memory that we are analyzing.
|
|
MarkUninitializedInst *MemoryInst;
|
|
|
|
/// This is the base type of the memory allocation.
|
|
SILType MemorySILType;
|
|
|
|
/// This is the count of elements being analyzed. For memory objects that are
|
|
/// tuples, this is the flattened element count. For 'self' members in init
|
|
/// methods, this is the local field count (+1 for super/self classes were
|
|
/// initialized).
|
|
unsigned NumElements;
|
|
|
|
/// True if the memory object being analyzed represents a 'let', which is
|
|
/// initialize-only (reassignments are not allowed).
|
|
bool IsLet = false;
|
|
|
|
/// True if NumElements has a dummy value in it to force a struct to be
|
|
/// non-empty.
|
|
bool HasDummyElement = false;
|
|
|
|
/// True if this object has a single user of type ProjectBoxInst.
|
|
bool IsBox = false;
|
|
|
|
public:
|
|
DIMemoryObjectInfo(MarkUninitializedInst *MemoryInst);
|
|
|
|
SILLocation getLoc() const { return MemoryInst->getLoc(); }
|
|
SILFunction &getFunction() const { return *MemoryInst->getFunction(); }
|
|
SILModule &getModule() const { return MemoryInst->getModule(); }
|
|
SILBasicBlock *getParentBlock() const { return MemoryInst->getParent(); }
|
|
|
|
/// Return the first instruction of the function containing the memory object.
|
|
SILInstruction *getFunctionEntryPoint() const;
|
|
|
|
CanType getASTType() const { return MemorySILType.getASTType(); }
|
|
SILType getType() const { return MemorySILType; }
|
|
|
|
/// Returns true if this memory object is of trivial type.
|
|
bool hasTrivialType() const { return MemorySILType.isTrivial(getFunction()); }
|
|
|
|
/// Returns true if NumElements has a dummy value in it to force a struct to
|
|
/// be non-empty.
|
|
bool hasDummyElement() const { return HasDummyElement; }
|
|
|
|
/// Return the actual 'uninitialized' memory. In the case of alloc_ref,
|
|
/// alloc_stack, this always just returns the actual mark_uninitialized
|
|
/// instruction. For alloc_box though it returns the project_box associated
|
|
/// with the memory info.
|
|
SingleValueInstruction *getUninitializedValue() const;
|
|
|
|
/// Return the number of elements, without the extra "super.init" tracker in
|
|
/// initializers of derived classes.
|
|
unsigned getNumMemoryElements() const {
|
|
return NumElements - (unsigned)isDerivedClassSelf();
|
|
}
|
|
|
|
/// Return the number of elements, including the extra "super.init" tracker in
|
|
/// initializers of derived classes.
|
|
///
|
|
/// \see getNumMemoryElements() for the number of elements, excluding the
|
|
/// extra "super.init" tracker in the initializers of derived classes.
|
|
unsigned getNumElements() const { return NumElements; }
|
|
|
|
/// Return true if this is 'self' in any kind of initializer.
|
|
bool isAnyInitSelf() const {
|
|
return !MemoryInst->isVar() && !MemoryInst->isOut();
|
|
}
|
|
|
|
/// Return uninitialized value of 'self' if current memory object
|
|
/// is located in an initializer (of any kind).
|
|
SingleValueInstruction *findUninitializedSelfValue() const;
|
|
|
|
/// True if the memory object is the 'self' argument of a struct initializer.
|
|
bool isStructInitSelf() const {
|
|
if (MemoryInst->isRootSelf() || MemoryInst->isCrossModuleRootSelf()) {
|
|
if (auto decl = getASTType()->getAnyNominal()) {
|
|
if (isa<StructDecl>(decl)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// True if the memory object is the 'self' argument of a non-delegating
|
|
/// cross-module struct initializer.
|
|
bool isCrossModuleStructInitSelf() const {
|
|
if (MemoryInst->isCrossModuleRootSelf()) {
|
|
assert(isStructInitSelf());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// True if the memory object is the 'self' argument of a class designated
|
|
/// initializer.
|
|
bool isClassInitSelf() const {
|
|
if (MemoryInst->isDelegatingSelf())
|
|
return false;
|
|
|
|
if (!MemoryInst->isVar() && !MemoryInst->isOut()) {
|
|
if (auto decl = getASTType()->getAnyNominal()) {
|
|
if (isa<ClassDecl>(decl)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// Returns the initializer if the memory use is 'self' and appears in an
|
|
/// actor's initializer. Otherwise, returns nullptr.
|
|
ConstructorDecl *getActorInitSelf() const;
|
|
|
|
/// True if this memory object is the 'self' of a derived class initializer.
|
|
bool isDerivedClassSelf() const { return MemoryInst->isDerivedClassSelf(); }
|
|
|
|
/// True if this memory object is the 'self' of a derived class initializer for
|
|
/// which we can assume that all ivars have been initialized.
|
|
bool isDerivedClassSelfOnly() const {
|
|
return MemoryInst->isDerivedClassSelfOnly();
|
|
}
|
|
|
|
/// True if this memory object is the 'self' of a derived class init method,
|
|
/// regardless of whether we're tracking ivar initializations or not.
|
|
bool isAnyDerivedClassSelf() const {
|
|
return MemoryInst->isDerivedClassSelf() ||
|
|
MemoryInst->isDerivedClassSelfOnly();
|
|
}
|
|
|
|
/// True if this memory object is the 'self' of a root class init method.
|
|
bool isRootClassSelf() const {
|
|
return isClassInitSelf() && MemoryInst->isRootSelf();
|
|
}
|
|
|
|
/// True if this memory object is the 'self' of a non-root class init method.
|
|
bool isNonRootClassSelf() const {
|
|
return isClassInitSelf() && !MemoryInst->isRootSelf();
|
|
}
|
|
|
|
/// True if this is a delegating initializer, one that calls 'self.init'.
|
|
bool isDelegatingInit() const {
|
|
return MemoryInst->isDelegatingSelf() ||
|
|
MemoryInst->isDelegatingSelfAllocated();
|
|
}
|
|
|
|
/// True if this is an initializer that initializes stored properties.
|
|
bool isNonDelegatingInit() const {
|
|
switch (MemoryInst->getMarkUninitializedKind()) {
|
|
case MarkUninitializedInst::Var:
|
|
case MarkUninitializedInst::Out:
|
|
return false;
|
|
case MarkUninitializedInst::RootSelf:
|
|
case MarkUninitializedInst::CrossModuleRootSelf:
|
|
case MarkUninitializedInst::DerivedSelf:
|
|
case MarkUninitializedInst::DerivedSelfOnly:
|
|
return true;
|
|
case MarkUninitializedInst::DelegatingSelf:
|
|
case MarkUninitializedInst::DelegatingSelfAllocated:
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool isRootSelf() const {
|
|
return MemoryInst->getMarkUninitializedKind() ==
|
|
MarkUninitializedInst::RootSelf;
|
|
}
|
|
|
|
bool isDelegatingSelfAllocated() const {
|
|
return MemoryInst->isDelegatingSelfAllocated();
|
|
}
|
|
|
|
bool isOut() const { return MemoryInst->isOut(); }
|
|
|
|
enum class EndScopeKind { Borrow, Access };
|
|
|
|
/// Given an element number (in the flattened sense) return a pointer to a
|
|
/// leaf element of the specified number.
|
|
SILValue emitElementAddressForDestroy(
|
|
unsigned TupleEltNo, SILLocation Loc, SILBuilder &B,
|
|
SmallVectorImpl<std::pair<SILValue, EndScopeKind>> &EndScopeList) const;
|
|
|
|
/// Return the swift type of the specified element.
|
|
SILType getElementType(unsigned EltNo) const;
|
|
|
|
/// Push the symbolic path name to the specified element number onto the
|
|
/// specified std::string. If the actual decl (or a subelement thereof) can
|
|
/// be determined, return it. Otherwise, return null.
|
|
ValueDecl *getPathStringToElement(unsigned Element,
|
|
std::string &Result) const;
|
|
|
|
/// If the specified value is a 'let' property in an initializer, return true.
|
|
bool isElementLetProperty(unsigned Element) const;
|
|
};
|
|
|
|
enum DIUseKind {
|
|
/// The instruction is a Load.
|
|
Load,
|
|
|
|
/// The instruction is either an initialization or an assignment, we don't
|
|
/// know which. This classification only happens with values of trivial type
|
|
/// where the different isn't significant.
|
|
InitOrAssign,
|
|
|
|
/// The instruction is an initialization of the tuple element.
|
|
Initialization,
|
|
|
|
/// The instruction is an assignment, overwriting an already initialized
|
|
/// value.
|
|
Assign,
|
|
|
|
/// The instruction is a setter call for a computed property after all of
|
|
/// self is initialized. This is used for property wrappers and for init
|
|
/// accessors.
|
|
Set,
|
|
|
|
/// The instruction is a store to a member of a larger struct value.
|
|
PartialStore,
|
|
|
|
/// This instruction is an init, assignment, or store to a
|
|
/// @_compilerInitialized field that was _not_ automatically generated
|
|
BadExplicitStore,
|
|
|
|
/// An 'inout' argument of a function application.
|
|
InOutArgument,
|
|
|
|
/// An 'inout' self argument of a function application.
|
|
InOutSelfArgument,
|
|
|
|
/// An indirect 'in' parameter of an Apply instruction.
|
|
IndirectIn,
|
|
|
|
/// This instruction is a general escape of the value, e.g. a call to a
|
|
/// closure that captures it.
|
|
Escape,
|
|
|
|
/// This instruction is a call to 'self.init' in a delegating initializer,
|
|
/// or a call to 'super.init' in a designated initializer of a derived class..
|
|
SelfInit,
|
|
|
|
/// This instruction is a load that's only used to answer a `type(of: self)`
|
|
/// question.
|
|
LoadForTypeOfSelf,
|
|
|
|
/// This instruction is a value_metatype on the address of 'self'.
|
|
TypeOfSelf,
|
|
|
|
/// This instruction is the builtin for flow-sensitive current isolation
|
|
/// within an actor initializer. It will be replaced with either a copy of
|
|
/// its argument (injected into an (any Actor)?) or nil.
|
|
FlowSensitiveSelfIsolation,
|
|
};
|
|
|
|
/// This struct represents a single classified access to the memory object
|
|
/// being analyzed, along with classification information about the access.
|
|
struct DIMemoryUse {
|
|
/// This is the instruction accessing the memory.
|
|
SILInstruction *Inst;
|
|
|
|
/// This is what kind of access it is, load, store, escape, etc.
|
|
DIUseKind Kind;
|
|
|
|
/// For memory objects of (potentially recursive) tuple type, this keeps
|
|
/// track of which tuple elements are affected.
|
|
unsigned FirstElement, NumElements;
|
|
|
|
NullablePtr<VarDecl> Field;
|
|
|
|
DIMemoryUse(SILInstruction *Inst, DIUseKind Kind, unsigned FE, unsigned NE,
|
|
NullablePtr<VarDecl> Field = 0)
|
|
: Inst(Inst), Kind(Kind), FirstElement(FE), NumElements(NE),
|
|
Field(Field) {}
|
|
|
|
DIMemoryUse() : Inst(nullptr) {}
|
|
|
|
bool isInvalid() const { return Inst == nullptr; }
|
|
bool isValid() const { return Inst != nullptr; }
|
|
|
|
bool usesElement(unsigned i) const {
|
|
return i >= FirstElement &&
|
|
i < static_cast<unsigned>(FirstElement + NumElements);
|
|
}
|
|
|
|
/// onlyTouchesTrivialElements - Return true if all of the accessed elements
|
|
/// have trivial type and the access itself is a trivial instruction.
|
|
bool onlyTouchesTrivialElements(const DIMemoryObjectInfo &MemoryInfo) const;
|
|
|
|
/// getElementBitmask - Return a bitmask with the touched tuple elements
|
|
/// set.
|
|
APInt getElementBitmask(unsigned NumMemoryTupleElements) const {
|
|
return APInt::getBitsSet(NumMemoryTupleElements, FirstElement,
|
|
FirstElement + NumElements);
|
|
}
|
|
};
|
|
|
|
struct DIElementUseInfo {
|
|
SmallVector<DIMemoryUse, 16> Uses;
|
|
SmallVector<SILInstruction *, 4> Releases;
|
|
TinyPtrVector<SILInstruction *> StoresToSelf;
|
|
|
|
void trackUse(DIMemoryUse Use) { Uses.push_back(Use); }
|
|
|
|
void trackDestroy(SILInstruction *Destroy) { Releases.push_back(Destroy); }
|
|
|
|
void trackStoreToSelf(SILInstruction *I);
|
|
};
|
|
|
|
/// collectDIElementUsesFrom - Analyze all uses of the specified allocation
|
|
/// instruction (alloc_box, alloc_stack or mark_uninitialized), classifying them
|
|
/// and storing the information found into the Uses and Releases lists.
|
|
void collectDIElementUsesFrom(const DIMemoryObjectInfo &MemoryInfo,
|
|
DIElementUseInfo &UseInfo);
|
|
|
|
} // end namespace ownership
|
|
} // end namespace swift
|
|
|
|
#endif
|