Files
swift-mirror/include/swift/SILOptimizer/Analysis/RCIdentityAnalysis.h
Michael Gottesman 2daf1d2050 [opt-remark] When looking for debug_value users, look modulo RC Identity preserving users.
A key concept in late ARC optimization is "RC Identity". In short, a result of
an instruction is rc-identical to an operand of the instruction if one can
safely move a retain (release) from before the instruction on the result to one
after on the operand without changing the program semantics. This creates a
simple model where one can work on equivalence classes of rc-identical values
(using a dominating definition generally as the representative) and thus
optimize/pair retain, release.

When preparing for late ARC optimization, the optimizer will normalize aggregate
ARC operations (retain_value, release_value) into singular strong_retain,
strong_release operations on leaf types of the aggregate that are
non-trivial. As an example, a retain_value on a KlassPair would be canonicalized
into two strong_retain, one for the lhs and one for the rhs. When this is done,
the optimizer generally just creates new struct_extract at the point where the
retain is. In such a case, we may have that the debug_value for the underlying
type is actually on a reformed aggregate whose underlying parts we are
retaining:

```
bb0(%0 : $Builtin.NativeObject):
  strong_retain %0
  %1 = struct $Array(%0 : $Builtin.NativeObject, ...)
  debug_value %1 : $Array, ...
```

By looking through RC identical uses, we can handle a large subset of these
cases without much effort: ones were there is a single owning pointer like Array.
To handle more complex cases we would have to calculate an inverse access path needed to get
back to our value and somehow deal with all of the complexity therein (I am sure
we can do it I just haven't thought through all of the details).

The only interesting behavior that this results in is that when we emit
diagnostics, we just use the rc-identical transitive use debug_value's name
without a projection path. This is because the source location associated with
that debug_value is with a separate value that is rc-identical to the actual
value that we visited during our opt-remark traversal up the def-use
graph. Consider the following example below, noting the comments that show in
the SIL itself what I attempted to explain above.

```
struct KlassPair {
  var lhs: Klass
  var rhs: Klass
}

struct StateWithOwningPointer {
  var state: TrivialState
  var owningPtr: Klass
}

sil @theFunction : $@convention(thin) () -> () {
bb0:
  %0 = apply %getKlassPair() : $@convention(thin) () -> @owned KlassPair
  // This debug_value's name can be combined...
  debug_value %0 : $KlassPair, name "myPair"
  // ... with the access path from the struct_extract here...
  %1 = struct_extract %0 : $KlassPair, #KlassPair.lhs
  // ... to emit a nice diagnostic that 'myPair.lhs' is being retained.
  strong_retain %1 : $Klass

  // In contrast in the case below, we rely on looking through rc-identity uses
  // to find the debug_value. In this case, the source info associated with the
  // debug_value (%2) is no longer associated with the underlying access path we
  // have been tracking upwards (%1 is in our access path list). Instead, we
  // know that the debug_value is rc-identical to whatever value we were
  // originally tracking up (%1) and thus the correct identifier to use is the
  // direct name of the identifier alone (without access path) since that source
  // identifier must be some value in the source that by itself is rc-identical
  // to whatever is being manipulated. Thus if we were to emit the access path
  // here for na rc-identical use we would get "myAdditionalState.owningPtr"
  // which is misleading since ArrayWrapperWithMoreState does not have a field
  // named 'owningPtr', its subfield array does. That being said since
  // rc-identity means a retain_value on the value with the debug_value upon it
  // is equivalent to the access path value we found by walking up the def-use
  // graph from our strong_retain's operand.
  %0a = apply %getStateWithOwningPointer() : $@convention(thin) () -> @owned StateWithOwningPointer
  %1 = struct_extract %0a : $StateWithOwningPointer, #StateWithOwningPointer.owningPtr
  strong_retain %1 : $Klass
  %2 = struct $Array(%0 : $Builtin.NativeObject, ...)
  %3 = struct $ArrayWrapperWithMoreState(%2 : $Array, %moreState : MoreState)
  debug_value %2 : $ArrayWrapperWithMoreState, name "myAdditionalState"
}
```
2020-09-01 23:25:59 -07:00

139 lines
4.7 KiB
C++

//===--- RCIdentityAnalysis.h -----------------------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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 is an analysis that determines the ref count identity (i.e. gc root) of
// a pointer. Any values with the same ref count identity are able to be
// retained and released interchangeably.
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_SILOPTIMIZER_ANALYSIS_RCIDENTITYANALYSIS_H
#define SWIFT_SILOPTIMIZER_ANALYSIS_RCIDENTITYANALYSIS_H
#include "swift/SIL/SILValue.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SILOptimizer/Analysis/Analysis.h"
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
#include "swift/SILOptimizer/PassManager/PassManager.h"
namespace swift {
/// Limit the size of the rc identity cache. We keep a cache per function.
constexpr unsigned MaxRCIdentityCacheSize = 64;
class DominanceAnalysis;
/// This class is a simple wrapper around an identity cache.
class RCIdentityFunctionInfo {
llvm::DenseSet<SILArgument *> VisitedArgs;
// RC identity cache.
llvm::DenseMap<SILValue, SILValue> RCCache;
DominanceAnalysis *DA;
/// This number is arbitrary and conservative. At some point if compile time
/// is not an issue, this value should be made more aggressive (i.e. greater).
enum { MaxRecursionDepth = 16 };
public:
RCIdentityFunctionInfo(DominanceAnalysis *D) : VisitedArgs(),
DA(D) {}
SILValue getRCIdentityRoot(SILValue V);
/// Return all recursive users of V, looking through users which propagate
/// RCIdentity.
///
/// *NOTE* This ignores obvious ARC escapes where the a potential
/// user of the RC is not managed by ARC. For instance
/// unchecked_trivial_bit_cast.
void getRCUses(SILValue V, SmallVectorImpl<Operand *> &Uses);
/// A helper method that calls getRCUses and then maps each operand to the
/// operands user and then uniques the list.
///
/// *NOTE* The routine asserts that the passed in Users array is empty for
/// simplicity. If needed this can be changed, but it is not necessary given
/// current uses.
void getRCUsers(SILValue V, SmallVectorImpl<SILInstruction *> &Users);
/// Like getRCUses except uses a callback to prevent the need for an
/// intermediate array.
void visitRCUses(SILValue V, function_ref<void(Operand *)> Visitor);
void handleDeleteNotification(SILNode *node) {
auto value = dyn_cast<ValueBase>(node);
if (!value)
return;
// Check the cache. If we don't find it, there is nothing to do.
auto Iter = RCCache.find(SILValue(value));
if (Iter == RCCache.end())
return;
// Then erase Iter from the cache.
RCCache.erase(Iter);
}
private:
SILValue getRCIdentityRootInner(SILValue V, unsigned RecursionDepth);
SILValue stripRCIdentityPreservingOps(SILValue V, unsigned RecursionDepth);
SILValue stripRCIdentityPreservingArgs(SILValue V, unsigned RecursionDepth);
SILValue stripOneRCIdentityIncomingValue(SILArgument *Arg, SILValue V);
bool findDominatingNonPayloadedEdge(SILBasicBlock *IncomingEdgeBB,
SILValue RCIdentity);
};
class RCIdentityAnalysis : public FunctionAnalysisBase<RCIdentityFunctionInfo> {
DominanceAnalysis *DA;
public:
RCIdentityAnalysis(SILModule *)
: FunctionAnalysisBase<RCIdentityFunctionInfo>(
SILAnalysisKind::RCIdentity),
DA(nullptr) {}
RCIdentityAnalysis(const RCIdentityAnalysis &) = delete;
RCIdentityAnalysis &operator=(const RCIdentityAnalysis &) = delete;
virtual void handleDeleteNotification(SILNode *node) override {
// If the parent function of this instruction was just turned into an
// external declaration, bail. This happens during SILFunction destruction.
SILFunction *F = node->getFunction();
if (F->isExternalDeclaration()) {
return;
}
get(F)->handleDeleteNotification(node);
}
virtual bool needsNotifications() override { return true; }
static bool classof(const SILAnalysis *S) {
return S->getKind() == SILAnalysisKind::RCIdentity;
}
virtual void initialize(SILPassManager *PM) override;
virtual std::unique_ptr<RCIdentityFunctionInfo>
newFunctionAnalysis(SILFunction *F) override {
return std::make_unique<RCIdentityFunctionInfo>(DA);
}
virtual bool shouldInvalidate(SILAnalysis::InvalidationKind K) override {
return true;
}
};
} // end swift namespace
#endif