Files
swift-mirror/include/swift/SIL/OptimizationRemark.h
Michael Gottesman 804bcac1e6 [assembly-vision] Change release to do backwards then forwards when inferring source locs.
TLDR: I fixed a whole in the assembly-vision opt-remark pass where we were not
emitting a remark for end of scope instructions at the beginning of blocks. Now
all of these instructions (strong_release, end_access) should always reliably
have a remark emitted for them.

----

I think that this is a pragmatic first solution to the problem of
strong_release, release_value being the first instruction of a block. For those
who are unaware, this issue is that for a long time we have searched backwards
first for "end of scope" like instructions. This then allows us to identify the
"end of scope" instruction as happening at the end of the previous statement
which is where the developer thinks it should be:

```
var global: Klass
func bar() -> @owned Klass { global }
func foo() {
   // We want the remark for the
   bar()                          // expected-remark {{retain}}
}
```

This makes sense since we want to show end of scope instructions as being
applied to the earlier code whose scope it is ending. We can be clear that it is
at the end of the statement by placing the carrot on the end of statement
SourceLoc so there isn't any confusion upon whether or not

That generally has delivered nice looking results, but what if our release is
the first instruction in the block? In that case, we do not have any instruction
that we can immediately use, so traditionally we just gave up and didn't emit
anything. This is not an acceptable solution! We should be able to emit
something for every retain/release in the program if we want users to be able to
rely upon this! Thus we need to be able to get source location information from
somewhere around

First before we begin, my approach here is informed by my seeing over time that
the optimizer does a pretty good job of not breaking SourceLoc info for
terminators.

With that in mind, there are two possible approaches here: using the terminator
from the previous block and searching forward at worst taking the SourceLoc of
the current block's terminator (or earlier if we find a good SourceLoc). I
wasn't sure what the correct thing to do was at the time so I didn't fix the
issue. After some thought, I realized that the correct solution is to if we fail
a backwards search, search forwards. The reason why is that since our remarks
runs late in the optimization pipeline, there is a very high likelihood that if
we aren't folded into our previous block that there is a true need in the
program for conditional control flow here. We want to avoid placing the release
out of such pieces of code since it is misleading to the user:

```

In this example there is a release inside the case for .x but none for .y. In
that case it is possible that we get a release for .f since payload is passed in
at +1 at SILGen time. In such a case, using the terminator of the previous block
would mean that we would have the release be marked as on payload instead of
inside the case of .x. By using the terminator of the releases block, we can

switch payload {
case let .x(f):
  ...
case let .y:
  ...
}
```

So using the terminator from the previous block would be
  misleading to the user. Instead it is better to pick a location after the release that way
  we know at least the instruction we are inferring from must in some sense be

With this fix, we should not emit locations for all retains, releases. We may
not identify a good source loc for all of them, but we will identify them.

optimization pipeline, if our block was not folded into the previous block there
is a very high liklihood that there is some sort of conditional control flow
that is truly necessary in the program. If we

this
generally implies that there is a real side effect in the program that is
requiring conditional code execution (since the optimizer would have folded it).

The reason why is that we are at least going to hit a terminator or a
side-effect having instruction that generally have debug info preserved by the
optimizer.
2021-07-02 11:51:09 -07:00

352 lines
12 KiB
C++

//===--- OptimizationRemark.h - Optimization diagnostics --------*- 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
//
//===----------------------------------------------------------------------===//
//
/// \file
/// This file defines the remark type and the emitter class that passes can use
/// to emit optimization diagnostics.
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_SIL_OPTIMIZATIONREMARKEMITTER_H
#define SWIFT_SIL_OPTIMIZATIONREMARKEMITTER_H
#include "swift/AST/SemanticAttrs.h"
#include "swift/Basic/SourceLoc.h"
#include "swift/Demangling/Demangler.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILBasicBlock.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SIL/SILModule.h"
#include "llvm/ADT/StringRef.h"
namespace swift {
class SILFunction;
namespace OptRemark {
struct ArgumentKeyKind {
enum InnerTy {
// Just assume this is a normal msg that we are emitting.
Default,
// Assume this is a note that should be emitted as a separate
// diagnostic when emitting diagnostics. Do nothing special
// along the backend path.
Note,
// Assume that this is a note that should be emitted as a separate
// diagnostic but that doesn't have its own source loc: we should reuse the
// one for the original remark.
//
// This is intended to be used in situations where one needs to emit a
// "note" warning due to us not being able to infer a part of our
// opt-remark.
ParentLocNote,
};
InnerTy innerValue;
ArgumentKeyKind(InnerTy value) : innerValue(value) {}
operator InnerTy() const { return innerValue; }
/// Return true if this argument is meant to be a separate diagnostic when we
/// emit diagnostics but when we emit to the remark streamer (due to not
/// having support for this), we just emit the remark inline.
///
/// TODO: Unfortunate that this needs to be done.
bool isSeparateDiagnostic() const {
switch (innerValue) {
case InnerTy::Default:
return false;
case InnerTy::Note:
case InnerTy::ParentLocNote:
return true;
}
llvm_unreachable("Covered switch isn't covered?!");
}
};
struct ArgumentKey {
ArgumentKeyKind kind;
std::string data;
ArgumentKey(ArgumentKeyKind kind, StringRef data) : kind(kind), data(data) {}
ArgumentKey(ArgumentKeyKind::InnerTy kind, StringRef data)
: kind(kind), data(data) {}
ArgumentKey(ArgumentKey kind, StringRef data) : kind(kind.kind), data(data) {}
};
/// Used in the streaming interface as the general argument type. It
/// internally converts everything into a key-value pair.
struct Argument {
ArgumentKey key;
std::string val;
/// If set, the debug location corresponding to the value.
SourceLoc loc;
explicit Argument(StringRef str = "")
: Argument({ArgumentKeyKind::Default, "String"}, str) {}
Argument(StringRef key, StringRef val)
: Argument({ArgumentKeyKind::Default, key}, val) {}
Argument(ArgumentKey key, StringRef val) : key(key), val(val) {}
Argument(StringRef key, int n);
Argument(StringRef key, long n);
Argument(StringRef key, long long n);
Argument(StringRef key, unsigned n);
Argument(StringRef key, unsigned long n);
Argument(StringRef key, unsigned long long n);
Argument(StringRef key, SILFunction *f)
: Argument(ArgumentKey(ArgumentKeyKind::Default, key), f) {}
Argument(ArgumentKey key, SILFunction *f);
Argument(StringRef key, SILType ty);
Argument(StringRef key, CanType ty);
Argument(StringRef key, StringRef msg, const ValueDecl *decl)
: Argument(ArgumentKey(ArgumentKeyKind::Default, key), msg, decl) {}
Argument(ArgumentKey key, StringRef msg, const ValueDecl *decl)
: key(key), val(msg), loc(decl->getLoc()) {}
Argument(StringRef key, llvm::Twine &&msg, SILLocation loc)
: Argument(ArgumentKey(ArgumentKeyKind::Default, key), std::move(msg),
loc) {}
Argument(ArgumentKey key, llvm::Twine &&msg, SILLocation loc)
: key(key), val(msg.str()), loc(loc.getSourceLoc()) {}
};
/// Shorthand to insert named-value pairs.
using NV = Argument;
/// Inserting this into a Remark indents the text when printed as a debug
/// message.
struct IndentDebug {
explicit IndentDebug(unsigned width) : width(width) {}
unsigned width;
};
enum class SourceLocPresentationKind {
StartRange,
EndRange,
};
enum class SourceLocInferenceBehavior : unsigned {
None = 0,
ForwardScan = 0x1,
BackwardScan = 0x2,
ForwardScan2nd = 0x4,
AlwaysInfer = 0x8,
ForwardThenBackwards = ForwardScan | BackwardScan,
BackwardsThenForwards = BackwardScan | ForwardScan2nd,
ForwardScanAlwaysInfer = ForwardScan | AlwaysInfer,
BackwardScanAlwaysInfer = BackwardScan | AlwaysInfer,
BackwardThenForwardAlwaysInfer = BackwardScan | ForwardScan2nd | AlwaysInfer,
};
inline SourceLocInferenceBehavior operator&(SourceLocInferenceBehavior lhs,
SourceLocInferenceBehavior rhs) {
auto lhsVal = std::underlying_type<SourceLocInferenceBehavior>::type(lhs);
auto rhsVal = std::underlying_type<SourceLocInferenceBehavior>::type(rhs);
return SourceLocInferenceBehavior(lhsVal & rhsVal);
}
/// Infer the proper SourceLoc to use for the given SILInstruction.
///
/// This means that if we have a valid location for the instruction, we just
/// return that. Otherwise, we have a runtime instruction that does not have a
/// valid source location. In such a case, we infer the source location from the
/// surrounding code. If we can not find any surrounding code, we return an
/// invalid SourceLoc.
SourceLoc inferOptRemarkSourceLoc(SILInstruction &i,
SourceLocInferenceBehavior inferBehavior,
SourceLocPresentationKind presentationKind =
SourceLocPresentationKind::StartRange);
/// The base class for remarks. This can be created by optimization passed to
/// report successful and unsuccessful optimizations. CRTP is used to preserve
/// the underlying type encoding the remark kind in the insertion operator.
template <typename DerivedT>
class Remark {
/// Arguments collected via the streaming interface.
SmallVector<Argument, 4> args;
/// The name of the pass generating the remark.
StringRef passName;
/// Textual identifier for the remark (single-word, camel-case). Can be used
/// by external tools reading the output file for optimization remarks to
/// identify the remark.
SmallString<32> identifier;
/// Source location for the diagnostics.
SourceLoc location;
/// The function for the diagnostics.
SILFunction *function;
/// The demangled name of \p Function.
SmallString<64> demangledFunctionName;
/// Indentation used if this remarks is printed as a debug message.
unsigned indentDebugWidth = 0;
protected:
Remark(StringRef identifier, SILInstruction &i,
SourceLocInferenceBehavior inferenceBehavior,
SourceLocPresentationKind locPresentationKind)
: identifier((Twine("sil.") + identifier).str()),
location(
inferOptRemarkSourceLoc(i, inferenceBehavior, locPresentationKind)),
function(i.getParent()->getParent()),
demangledFunctionName(Demangle::demangleSymbolAsString(
function->getName(),
Demangle::DemangleOptions::SimplifiedUIDemangleOptions())) {}
public:
DerivedT &operator<<(StringRef s) {
args.emplace_back(s);
return *static_cast<DerivedT *>(this);
}
DerivedT &operator<<(Argument a) {
args.push_back(std::move(a));
return *static_cast<DerivedT *>(this);
}
DerivedT &operator<<(IndentDebug indent) {
indentDebugWidth = indent.width;
return *static_cast<DerivedT *>(this);
}
StringRef getPassName() const { return passName; }
StringRef getIdentifier() const { return identifier; }
SILFunction *getFunction() const { return function; }
StringRef getDemangledFunctionName() const { return demangledFunctionName; }
SourceLoc getLocation() const { return location; }
std::string getMsg() const;
std::string getDebugMsg() const;
Remark<DerivedT> &getRemark() { return *this; }
SmallVector<Argument, 4> &getArgs() { return args; }
ArrayRef<Argument> getArgs() const { return args; }
void setPassName(StringRef name) { passName = name; }
};
/// Remark to report a successful optimization.
struct RemarkPassed : public Remark<RemarkPassed> {
RemarkPassed(StringRef id, SILInstruction &i)
: Remark(id, i, SourceLocInferenceBehavior::None,
SourceLocPresentationKind::StartRange) {}
RemarkPassed(StringRef id, SILInstruction &i,
SourceLocInferenceBehavior inferenceBehavior)
: Remark(id, i, inferenceBehavior,
SourceLocPresentationKind::StartRange) {}
RemarkPassed(StringRef id, SILInstruction &i,
SourceLocInferenceBehavior inferenceBehavior,
SourceLocPresentationKind locPresentationKind)
: Remark(id, i, inferenceBehavior, locPresentationKind) {}
};
/// Remark to report a unsuccessful optimization.
struct RemarkMissed : public Remark<RemarkMissed> {
RemarkMissed(StringRef id, SILInstruction &i)
: Remark(id, i, SourceLocInferenceBehavior::None,
SourceLocPresentationKind::StartRange) {}
RemarkMissed(StringRef id, SILInstruction &i,
SourceLocInferenceBehavior inferenceBehavior)
: Remark(id, i, inferenceBehavior,
SourceLocPresentationKind::StartRange) {}
RemarkMissed(StringRef id, SILInstruction &i,
SourceLocInferenceBehavior inferenceBehavior,
SourceLocPresentationKind locPresentationKind)
: Remark(id, i, inferenceBehavior, locPresentationKind) {}
};
/// Used to emit the remarks. Passes reporting remarks should create an
/// instance of this.
class Emitter {
SILFunction &fn;
std::string passName;
bool passedEnabled;
bool missedEnabled;
// Making these non-generic allows out-of-line definition.
void emit(const RemarkPassed &remark);
void emit(const RemarkMissed &remark);
static void emitDebug(const RemarkPassed &remark);
static void emitDebug(const RemarkMissed &remark);
template <typename RemarkT> bool isEnabled();
public:
Emitter(StringRef passName, SILFunction &fn);
/// Take a lambda that returns a remark which will be emitted. The
/// lambda is not evaluated unless remarks are enabled. Second argument is
/// only used to restrict this to functions.
template <typename T>
void emit(T remarkBuilder, decltype(remarkBuilder()) * = nullptr) {
using RemarkT = decltype(remarkBuilder());
// Avoid building the remark unless remarks are enabled.
if (isEnabled<RemarkT>() || fn.getModule().getSILRemarkStreamer()) {
auto rb = remarkBuilder();
rb.setPassName(passName);
emit(rb);
}
}
/// Emit an optimization remark or debug message.
template <typename T>
static void emitOrDebug(const char *passName, Emitter *emitter,
T remarkBuilder,
decltype(remarkBuilder()) * = nullptr) {
using RemarkT = decltype(remarkBuilder());
// Avoid building the remark unless remarks are enabled.
bool emitRemark =
emitter && (emitter->isEnabled<RemarkT>() ||
emitter->fn.getModule().getSILRemarkStreamer());
// Same for DEBUG.
bool shouldEmitDebug = false;
#ifndef NDEBUG
shouldEmitDebug |= llvm::DebugFlag && llvm::isCurrentDebugType(passName);
#endif // NDEBUG
if (emitRemark || shouldEmitDebug) {
auto r = remarkBuilder();
if (shouldEmitDebug)
emitDebug(r);
if (emitRemark) {
// If we have an Emitter use the PassName that was set up. DEBUG_TYPE
// may be different if a pass is calling other modules.
r.setPassName(emitter->passName);
emitter->emit(r);
}
}
}
};
#define REMARK_OR_DEBUG(...) \
OptRemark::Emitter::emitOrDebug(DEBUG_TYPE, __VA_ARGS__)
template <> inline bool Emitter::isEnabled<RemarkMissed>() {
return missedEnabled;
}
template <> inline bool Emitter::isEnabled<RemarkPassed>() {
return passedEnabled;
}
} // namespace OptRemark
} // namespace swift
#endif