mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
879 lines
31 KiB
C++
879 lines
31 KiB
C++
//===--- AssemblyVisionRemarkGenerator.cpp --------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
///
|
|
/// In this pass, we define the assembly-vision-remark-generator, a simple
|
|
/// SILVisitor that attempts to infer remarks for the user using heuristics.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#define DEBUG_TYPE "sil-assembly-vision-remark-gen"
|
|
|
|
#include "swift/AST/SemanticAttrs.h"
|
|
#include "swift/Basic/Defer.h"
|
|
#include "swift/SIL/DebugUtils.h"
|
|
#include "swift/SIL/DynamicCasts.h"
|
|
#include "swift/SIL/MemAccessUtils.h"
|
|
#include "swift/SIL/OptimizationRemark.h"
|
|
#include "swift/SIL/PatternMatch.h"
|
|
#include "swift/SIL/Projection.h"
|
|
#include "swift/SIL/SILFunction.h"
|
|
#include "swift/SIL/SILInstruction.h"
|
|
#include "swift/SIL/SILModule.h"
|
|
#include "swift/SIL/SILVisitor.h"
|
|
#include "swift/SILOptimizer/Analysis/RCIdentityAnalysis.h"
|
|
#include "swift/SILOptimizer/PassManager/Passes.h"
|
|
#include "swift/SILOptimizer/PassManager/Transforms.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
using namespace swift;
|
|
using namespace swift::PatternMatch;
|
|
|
|
static llvm::cl::opt<bool> ForceVisitImplicitAutogeneratedFunctions(
|
|
"assemblyvisionremarkgen-visit-implicit-autogen-funcs", llvm::cl::Hidden,
|
|
llvm::cl::desc(
|
|
"Emit opt remarks even on implicit and autogenerated functions"),
|
|
llvm::cl::init(false));
|
|
|
|
static llvm::cl::opt<bool> DecllessDebugValueUseSILDebugInfo(
|
|
"assemblyvisionremarkgen-declless-debugvalue-use-sildebugvar-info",
|
|
llvm::cl::Hidden,
|
|
llvm::cl::desc(
|
|
"If a debug_value does not have a decl, infer a value with a name from "
|
|
"that info that has a loc set to the loc of the debug_value "
|
|
"instruction itself. This is for testing purposes so it is easier to "
|
|
"write SIL test cases for this pass"),
|
|
llvm::cl::init(false));
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Value To Decl Inferrer
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
struct ValueToDeclInferrer {
|
|
using Argument = OptRemark::Argument;
|
|
using ArgumentKeyKind = OptRemark::ArgumentKeyKind;
|
|
|
|
SmallVector<std::pair<SILType, Projection>, 32> accessPath;
|
|
SmallVector<Operand *, 32> rcIdenticalSecondaryUseSearch;
|
|
RCIdentityFunctionInfo &rcfi;
|
|
|
|
ValueToDeclInferrer(RCIdentityFunctionInfo &rcfi) : rcfi(rcfi) {}
|
|
|
|
/// Given a value, attempt to infer a conservative list of decls that the
|
|
/// passed in value could be referring to. This is done just using heuristics
|
|
bool infer(ArgumentKeyKind keyKind, SILValue value,
|
|
SmallVectorImpl<Argument> &resultingInferredDecls,
|
|
bool allowSingleRefEltAddrPeek = false);
|
|
|
|
/// Print out a note to \p stream that beings at decl and then if
|
|
/// useProjectionPath is set to true iterates the accessPath we computed for
|
|
/// decl producing a segmented access path, e.x.: "of 'x.lhs.ivar'".
|
|
///
|
|
/// The reason why one may not want to emit a projection path note here is if
|
|
/// one found an debug_value on a value that is rc-identical to the actual
|
|
/// value associated with the current projection path. Consider the following
|
|
/// SIL:
|
|
///
|
|
/// 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 this case, 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 since that source identifier must be some value
|
|
/// // in the source that by itself is rc-identical to whatever is being
|
|
/// // manipulated.
|
|
/// //
|
|
/// // The reason why we must do this is due to the behavior of the late
|
|
/// // optimizer and how it forms these patterns in the code.
|
|
/// %0a = apply %getStateWithOwningPointer() : $@convention(thin) () ->
|
|
/// @owned StateWithOwningPointer %1 = struct_extract %0a :
|
|
/// $StateWithOwningPointer, #StateWithOwningPointer.owningPtr
|
|
/// strong_retain %1 : $Klass
|
|
/// %2 = struct $Array(%0 : $Builtin.NativeObject, ...)
|
|
/// debug_value %2 : $Array, ...
|
|
/// }
|
|
void printNote(llvm::raw_string_ostream &stream, StringRef name,
|
|
bool shouldPrintAccessPath = true);
|
|
|
|
/// Convenience overload that calls:
|
|
///
|
|
/// printNote(stream, decl->getBaseName().userFacingName(),
|
|
/// shouldPrintAccessPath).
|
|
void printNote(llvm::raw_string_ostream &stream, const ValueDecl *decl,
|
|
bool shouldPrintAccessPath = true) {
|
|
printNote(stream, decl->getBaseName().userFacingName(),
|
|
shouldPrintAccessPath);
|
|
}
|
|
|
|
/// Print out non-destructively the current access path we have found to
|
|
/// stream.
|
|
void printAccessPath(llvm::raw_string_ostream &stream);
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
void ValueToDeclInferrer::printAccessPath(llvm::raw_string_ostream &stream) {
|
|
for (auto &pair : accessPath) {
|
|
auto baseType = pair.first;
|
|
auto &proj = pair.second;
|
|
stream << ".";
|
|
|
|
// WARNING: This must be kept insync with isSupportedProjection!
|
|
switch (proj.getKind()) {
|
|
case ProjectionKind::BlockStorageCast:
|
|
stream << "project_block_storage<" << proj.getCastType(baseType) << ">";
|
|
continue;
|
|
case ProjectionKind::Upcast:
|
|
stream << "upcast<" << proj.getCastType(baseType) << ">";
|
|
continue;
|
|
case ProjectionKind::RefCast:
|
|
stream << "refcast<" << proj.getCastType(baseType) << ">";
|
|
continue;
|
|
case ProjectionKind::BitwiseCast:
|
|
stream << "bitwise_cast<" << proj.getCastType(baseType) << ">";
|
|
continue;
|
|
case ProjectionKind::Struct:
|
|
case ProjectionKind::Class:
|
|
stream << proj.getVarDecl(baseType)->getBaseName();
|
|
continue;
|
|
case ProjectionKind::Tuple:
|
|
stream << proj.getIndex();
|
|
continue;
|
|
case ProjectionKind::Enum:
|
|
stream << proj.getEnumElementDecl(baseType)->getBaseName();
|
|
continue;
|
|
|
|
// Object -> Address projections can never be looked through unless they are
|
|
// from a class where we have special logic for it only happening a single
|
|
// time.
|
|
case ProjectionKind::Box:
|
|
case ProjectionKind::Index:
|
|
case ProjectionKind::TailElems:
|
|
llvm_unreachable(
|
|
"Object -> Address projection should never be looked through!");
|
|
}
|
|
|
|
llvm_unreachable("Covered switch is not covered?!");
|
|
}
|
|
}
|
|
|
|
void ValueToDeclInferrer::printNote(llvm::raw_string_ostream &stream,
|
|
StringRef name,
|
|
bool shouldPrintAccessPath) {
|
|
stream << "of '" << name;
|
|
if (shouldPrintAccessPath)
|
|
printAccessPath(stream);
|
|
stream << "'";
|
|
}
|
|
|
|
// WARNING: This must be kept insync with ValueToDeclInferrer::printNote(...).
|
|
static SingleValueInstruction *isSupportedProjection(Projection p, SILValue v) {
|
|
switch (p.getKind()) {
|
|
case ProjectionKind::Upcast:
|
|
case ProjectionKind::RefCast:
|
|
case ProjectionKind::BlockStorageCast:
|
|
case ProjectionKind::BitwiseCast:
|
|
case ProjectionKind::Struct:
|
|
case ProjectionKind::Tuple:
|
|
case ProjectionKind::Enum:
|
|
return cast<SingleValueInstruction>(v);
|
|
// Object -> Address projections can never be looked through.
|
|
case ProjectionKind::Class:
|
|
case ProjectionKind::Box:
|
|
case ProjectionKind::Index:
|
|
case ProjectionKind::TailElems:
|
|
return nullptr;
|
|
}
|
|
llvm_unreachable("Covered switch is not covered?!");
|
|
}
|
|
|
|
static bool hasNonInlinedDebugScope(SILInstruction *i) {
|
|
if (auto *scope = i->getDebugScope())
|
|
return !scope->InlinedCallSite;
|
|
return false;
|
|
}
|
|
|
|
namespace {
|
|
|
|
/// A helper struct that attempts to infer the decl associated with a value from
|
|
/// one of its uses. It does this by searching the def-use graph locally for
|
|
/// debug_value instructions.
|
|
struct ValueUseToDeclInferrer {
|
|
using Argument = ValueToDeclInferrer::Argument;
|
|
using ArgumentKeyKind = ValueToDeclInferrer::ArgumentKeyKind;
|
|
|
|
SmallPtrSet<swift::SILInstruction *, 8> visitedDebugValueInsts;
|
|
ValueToDeclInferrer &object;
|
|
ArgumentKeyKind keyKind;
|
|
SmallVectorImpl<Argument> &resultingInferredDecls;
|
|
|
|
bool findDecls(Operand *use, SILValue value);
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
bool ValueUseToDeclInferrer::findDecls(Operand *use, SILValue value) {
|
|
// Skip type dependent operands.
|
|
if (use->isTypeDependent())
|
|
return false;
|
|
|
|
// Then see if we have a debug_value that is associated with a non-inlined
|
|
// debug scope. Such an instruction is an instruction that is from the
|
|
// current function.
|
|
auto debugInst = DebugVarCarryingInst(use->getUser());
|
|
if (!debugInst)
|
|
return false;
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Found DebugInst: " << **debugInst);
|
|
if (!hasNonInlinedDebugScope(*debugInst))
|
|
return false;
|
|
|
|
// See if we have already inferred this debug_value as a potential source
|
|
// for this instruction. In such a case, just return.
|
|
if (!visitedDebugValueInsts.insert(*debugInst).second)
|
|
return false;
|
|
|
|
if (auto *decl = debugInst.getDecl()) {
|
|
std::string msg;
|
|
{
|
|
llvm::raw_string_ostream stream(msg);
|
|
// If we are not a top level use, we must be a rc-identical transitive
|
|
// use. In such a case, we just print out the rc identical value
|
|
// without a projection path. This is because we now have a better
|
|
// name and the name is rc-identical to whatever was at the end of the
|
|
// projection path but is not at the end of that projection path.
|
|
object.printNote(stream, decl,
|
|
use->get() == value /*print projection path*/);
|
|
}
|
|
resultingInferredDecls.emplace_back(
|
|
OptRemark::ArgumentKey{keyKind, "InferredValue"}, std::move(msg), decl);
|
|
return true;
|
|
}
|
|
|
|
// If we did not have a decl, see if we were asked for testing
|
|
// purposes to use SILDebugInfo to create a placeholder inferred
|
|
// value.
|
|
if (!DecllessDebugValueUseSILDebugInfo)
|
|
return false;
|
|
|
|
auto varInfo = debugInst.getVarInfo();
|
|
if (!varInfo)
|
|
return false;
|
|
|
|
auto name = varInfo->Name;
|
|
if (name.empty())
|
|
return false;
|
|
|
|
std::string msg;
|
|
{
|
|
llvm::raw_string_ostream stream(msg);
|
|
object.printNote(stream, name,
|
|
use->get() == value /*print projection path*/);
|
|
}
|
|
resultingInferredDecls.push_back(Argument(
|
|
{keyKind, "InferredValue"}, std::move(msg), debugInst->getLoc()));
|
|
return true;
|
|
}
|
|
|
|
bool ValueToDeclInferrer::infer(
|
|
ArgumentKeyKind keyKind, SILValue value,
|
|
SmallVectorImpl<Argument> &resultingInferredDecls,
|
|
bool allowSingleRefEltAddrPeek) {
|
|
// Clear the stored access path at end of scope.
|
|
SWIFT_DEFER { accessPath.clear(); };
|
|
ValueUseToDeclInferrer valueUseInferrer{
|
|
{}, *this, keyKind, resultingInferredDecls};
|
|
bool foundSingleRefElementAddr = false;
|
|
|
|
// This is a linear IR traversal using a 'falling while loop'. That means
|
|
// every time through the loop we are trying to handle a case before we hit
|
|
// the bottom of the while loop where we always return true (since we did not
|
|
// hit a could not compute case). Reassign value and continue to go to the
|
|
// next step.
|
|
LLVM_DEBUG(llvm::dbgs() << "Searching for decls!\n");
|
|
while (true) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Visiting: " << *value);
|
|
|
|
// First check for "identified values" like arguments and global_addr.
|
|
if (auto *arg = dyn_cast<SILArgument>(value))
|
|
if (auto *decl = arg->getDecl()) {
|
|
std::string msg;
|
|
{
|
|
llvm::raw_string_ostream stream(msg);
|
|
printNote(stream, decl);
|
|
}
|
|
resultingInferredDecls.push_back(
|
|
Argument({keyKind, "InferredValue"}, std::move(msg), decl));
|
|
return true;
|
|
}
|
|
|
|
if (auto *ga = dyn_cast<GlobalAddrInst>(value))
|
|
if (auto *decl = ga->getReferencedGlobal()->getDecl()) {
|
|
std::string msg;
|
|
{
|
|
llvm::raw_string_ostream stream(msg);
|
|
printNote(stream, decl);
|
|
}
|
|
resultingInferredDecls.push_back(
|
|
Argument({keyKind, "InferredValue"}, std::move(msg), decl));
|
|
return true;
|
|
}
|
|
|
|
if (auto *ari = dyn_cast<AllocRefInst>(value)) {
|
|
if (auto *decl = ari->getDecl()) {
|
|
std::string msg;
|
|
{
|
|
llvm::raw_string_ostream stream(msg);
|
|
printNote(stream, decl);
|
|
}
|
|
resultingInferredDecls.push_back(
|
|
Argument({keyKind, "InferredValue"}, std::move(msg), decl));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (auto *abi = dyn_cast<AllocBoxInst>(value)) {
|
|
if (auto *decl = abi->getDecl()) {
|
|
std::string msg;
|
|
{
|
|
llvm::raw_string_ostream stream(msg);
|
|
printNote(stream, decl);
|
|
}
|
|
|
|
resultingInferredDecls.push_back(
|
|
Argument({keyKind, "InferredValue"}, std::move(msg), decl));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// A pattern that we see around empty array storage is:
|
|
//
|
|
// %0 = global_addr @_swiftEmptyArrayStorage : $*_SwiftEmptyArrayStorage
|
|
// %1 = address_to_pointer %0 : $*_SwiftEmptyArrayStorage to
|
|
// $Builtin.RawPointer %2 = raw_pointer_to_ref %1 : $Builtin.RawPointer to
|
|
// $__EmptyArrayStorage
|
|
//
|
|
// Recognize this case.
|
|
{
|
|
GlobalAddrInst *gai;
|
|
if (match(value, m_RawPointerToRefInst(
|
|
m_AddressToPointerInst(m_GlobalAddrInst(gai))))) {
|
|
if (auto *decl = gai->getReferencedGlobal()->getDecl()) {
|
|
std::string msg;
|
|
{
|
|
llvm::raw_string_ostream stream(msg);
|
|
printNote(stream, decl);
|
|
}
|
|
resultingInferredDecls.push_back(
|
|
Argument({keyKind, "InferredValue"}, std::move(msg), decl));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// We prefer decls not from uses since these are inherently noisier. Still,
|
|
// it is better than nothing.
|
|
bool foundDeclFromUse = false;
|
|
|
|
if (auto *asi = dyn_cast<AllocStackInst>(value)) {
|
|
if (auto *decl = asi->getDecl()) {
|
|
std::string msg;
|
|
{
|
|
llvm::raw_string_ostream stream(msg);
|
|
printNote(stream, decl);
|
|
}
|
|
resultingInferredDecls.push_back(
|
|
Argument({keyKind, "InferredValue"}, std::move(msg), decl));
|
|
return true;
|
|
}
|
|
|
|
// See if we have a single init alloc_stack and can infer a
|
|
// debug_value from that.
|
|
LLVM_DEBUG(llvm::dbgs() << "Checking for single init use!\n");
|
|
if (auto *initUse = getSingleInitAllocStackUse(asi)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Found one: " << *initUse->getUser());
|
|
if (auto *si = dyn_cast<StoreInst>(initUse->getUser())) {
|
|
for (auto *use : si->getSrc()->getUses()) {
|
|
foundDeclFromUse |= valueUseInferrer.findDecls(use, value);
|
|
}
|
|
}
|
|
if (auto *cai = dyn_cast<CopyAddrInst>(initUse->getUser())) {
|
|
for (auto *use : cai->getSrc()->getUses()) {
|
|
foundDeclFromUse |= valueUseInferrer.findDecls(use, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then visit our users (ignoring rc identical transformations) and see if
|
|
// we can find a debug_value that provides us with a decl we can use to
|
|
// construct an argument.
|
|
//
|
|
// The reason why we do this is that sometimes we reform a struct from its
|
|
// constituent parts and then construct the debug_value from that. For
|
|
// instance, if we FSOed.
|
|
rcfi.visitRCUses(value, [&](Operand *use) {
|
|
foundDeclFromUse |= valueUseInferrer.findDecls(use, value);
|
|
});
|
|
|
|
for (Operand *use : value->getUses()) {
|
|
if (auto *eir = dyn_cast<EndInitLetRefInst>(use->getUser())) {
|
|
rcfi.visitRCUses(eir, [&](Operand *use) {
|
|
foundDeclFromUse |= valueUseInferrer.findDecls(use, value);
|
|
});
|
|
}
|
|
}
|
|
|
|
// At this point, we could not infer any argument. See if we can look up the
|
|
// def-use graph and come up with a good location after looking through
|
|
// loads and projections.
|
|
if (auto *li = dyn_cast<LoadInst>(value)) {
|
|
value = stripAccessMarkers(li->getOperand());
|
|
continue;
|
|
}
|
|
|
|
if (auto proj = Projection(value)) {
|
|
if (auto *projInst = isSupportedProjection(proj, value)) {
|
|
value = projInst->getOperand(0);
|
|
accessPath.emplace_back(value->getType(), proj);
|
|
continue;
|
|
}
|
|
|
|
// Check if we had a ref_element_addr and our caller said that they were
|
|
// ok with skipping a single one.
|
|
//
|
|
// Examples of users: begin_access, end_access.
|
|
if (allowSingleRefEltAddrPeek &&
|
|
proj.getKind() == ProjectionKind::Class) {
|
|
if (!foundSingleRefElementAddr) {
|
|
value = cast<RefElementAddrInst>(value)->getOperand();
|
|
accessPath.emplace_back(value->getType(), proj);
|
|
foundSingleRefElementAddr = true;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: We could emit at this point a msg for temporary allocations.
|
|
|
|
// If we reached this point, we finished falling through the loop and return
|
|
// if we found any decls from uses. We always process everything so we /can/
|
|
// potentially emit multiple diagnostics.
|
|
return foundDeclFromUse;
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Opt Remark Generator Visitor
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
struct AssemblyVisionRemarkGeneratorInstructionVisitor
|
|
: public SILInstructionVisitor<
|
|
AssemblyVisionRemarkGeneratorInstructionVisitor> {
|
|
SILModule &mod;
|
|
OptRemark::Emitter ORE;
|
|
|
|
/// A class that we use to infer the decl that is associated with a
|
|
/// miscellaneous SIL value. This is just a heuristic that is to taste.
|
|
ValueToDeclInferrer valueToDeclInferrer;
|
|
|
|
AssemblyVisionRemarkGeneratorInstructionVisitor(SILFunction &fn,
|
|
RCIdentityFunctionInfo &rcfi)
|
|
: mod(fn.getModule()), ORE(DEBUG_TYPE, fn), valueToDeclInferrer(rcfi) {}
|
|
|
|
void visitStrongRetainInst(StrongRetainInst *sri);
|
|
void visitStrongReleaseInst(StrongReleaseInst *sri);
|
|
void visitRetainValueInst(RetainValueInst *rvi);
|
|
void visitReleaseValueInst(ReleaseValueInst *rvi);
|
|
void visitAllocRefInstBase(AllocRefInstBase *ari);
|
|
void visitAllocRefInst(AllocRefInst *ari);
|
|
void visitAllocRefDynamicInst(AllocRefDynamicInst *ari);
|
|
void visitAllocBoxInst(AllocBoxInst *abi);
|
|
void visitSILInstruction(SILInstruction *) {}
|
|
void visitBeginAccessInst(BeginAccessInst *bai);
|
|
void visitEndAccessInst(EndAccessInst *eai);
|
|
void visitCheckedCastAddrBranchInst(CheckedCastAddrBranchInst *ccabi);
|
|
void visitUnconditionalCheckedCastAddrInst(
|
|
UnconditionalCheckedCastAddrInst *uccai);
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
void AssemblyVisionRemarkGeneratorInstructionVisitor::
|
|
visitUnconditionalCheckedCastAddrInst(
|
|
UnconditionalCheckedCastAddrInst *uccai) {
|
|
ORE.emit([&]() {
|
|
using namespace OptRemark;
|
|
SmallVector<Argument, 8> inferredArgs;
|
|
bool foundArgs = valueToDeclInferrer.infer(
|
|
ArgumentKeyKind::Note, uccai->getSrc(), inferredArgs,
|
|
true /*allow single ref elt peek*/);
|
|
(void)foundArgs;
|
|
|
|
// Use the actual source loc of the
|
|
auto remark = RemarkMissed("memory", *uccai)
|
|
<< "unconditional runtime cast of value with type '"
|
|
<< NV("ValueType", uccai->getSrc()->getType()) << "' to '"
|
|
<< NV("CastType", uccai->getDest()->getType()) << "'";
|
|
for (auto arg : inferredArgs) {
|
|
remark << arg;
|
|
}
|
|
return remark;
|
|
});
|
|
}
|
|
|
|
void AssemblyVisionRemarkGeneratorInstructionVisitor::
|
|
visitCheckedCastAddrBranchInst(CheckedCastAddrBranchInst *ccabi) {
|
|
ORE.emit([&]() {
|
|
using namespace OptRemark;
|
|
SmallVector<Argument, 8> inferredArgs;
|
|
bool foundArgs = valueToDeclInferrer.infer(
|
|
ArgumentKeyKind::Note, ccabi->getSrc(), inferredArgs,
|
|
true /*allow single ref elt peek*/);
|
|
(void)foundArgs;
|
|
|
|
// Use the actual source loc of the
|
|
auto remark = RemarkMissed("memory", *ccabi)
|
|
<< "conditional runtime cast of value with type '"
|
|
<< NV("ValueType", ccabi->getSrc()->getType()) << "' to '"
|
|
<< NV("CastType", ccabi->getDest()->getType()) << "'";
|
|
for (auto arg : inferredArgs) {
|
|
remark << arg;
|
|
}
|
|
return remark;
|
|
});
|
|
}
|
|
|
|
void AssemblyVisionRemarkGeneratorInstructionVisitor::visitBeginAccessInst(
|
|
BeginAccessInst *bai) {
|
|
ORE.emit([&]() {
|
|
using namespace OptRemark;
|
|
SmallVector<Argument, 8> inferredArgs;
|
|
bool foundArgs = valueToDeclInferrer.infer(
|
|
ArgumentKeyKind::Note, bai->getOperand(), inferredArgs,
|
|
true /*allow single ref elt peek*/);
|
|
(void)foundArgs;
|
|
|
|
// Use the actual source loc of the
|
|
auto remark =
|
|
RemarkMissed("memory", *bai, SourceLocInferenceBehavior::ForwardScan)
|
|
<< "begin exclusive access to value of type '"
|
|
<< NV("ValueType", bai->getOperand()->getType()) << "'";
|
|
for (auto arg : inferredArgs) {
|
|
remark << arg;
|
|
}
|
|
return remark;
|
|
});
|
|
}
|
|
|
|
void AssemblyVisionRemarkGeneratorInstructionVisitor::visitEndAccessInst(
|
|
EndAccessInst *eai) {
|
|
ORE.emit([&]() {
|
|
using namespace OptRemark;
|
|
auto *bai = cast<BeginAccessInst>(eai->getOperand());
|
|
SmallVector<Argument, 8> inferredArgs;
|
|
bool foundArgs = valueToDeclInferrer.infer(
|
|
ArgumentKeyKind::Note, bai->getOperand(), inferredArgs,
|
|
true /*allow single ref elt peek*/);
|
|
(void)foundArgs;
|
|
|
|
// Use the actual source loc of the begin_access if it works. Otherwise,
|
|
// scan backwards.
|
|
auto remark =
|
|
RemarkMissed("memory", *eai,
|
|
SourceLocInferenceBehavior::BackwardThenForwardAlwaysInfer,
|
|
SourceLocPresentationKind::EndRange)
|
|
<< "end exclusive access to value of type '"
|
|
<< NV("ValueType", eai->getOperand()->getType()) << "'";
|
|
for (auto arg : inferredArgs) {
|
|
remark << arg;
|
|
}
|
|
return remark;
|
|
});
|
|
}
|
|
|
|
void AssemblyVisionRemarkGeneratorInstructionVisitor::visitStrongRetainInst(
|
|
StrongRetainInst *sri) {
|
|
ORE.emit([&]() {
|
|
using namespace OptRemark;
|
|
SmallVector<Argument, 8> inferredArgs;
|
|
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note,
|
|
sri->getOperand(), inferredArgs);
|
|
(void)foundArgs;
|
|
|
|
// Retains begin a lifetime scope so we infer scan forward.
|
|
auto remark =
|
|
RemarkMissed("memory", *sri,
|
|
SourceLocInferenceBehavior::ForwardScanAlwaysInfer)
|
|
<< "retain of type '" << NV("ValueType", sri->getOperand()->getType())
|
|
<< "'";
|
|
for (auto arg : inferredArgs) {
|
|
remark << arg;
|
|
}
|
|
return remark;
|
|
});
|
|
}
|
|
|
|
void AssemblyVisionRemarkGeneratorInstructionVisitor::visitStrongReleaseInst(
|
|
StrongReleaseInst *sri) {
|
|
ORE.emit([&]() {
|
|
using namespace OptRemark;
|
|
// Releases end a lifetime scope so we infer scan backward.
|
|
SmallVector<Argument, 8> inferredArgs;
|
|
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note,
|
|
sri->getOperand(), inferredArgs);
|
|
(void)foundArgs;
|
|
|
|
auto remark =
|
|
RemarkMissed("memory", *sri,
|
|
SourceLocInferenceBehavior::BackwardThenForwardAlwaysInfer,
|
|
SourceLocPresentationKind::EndRange)
|
|
<< "release of type '" << NV("ValueType", sri->getOperand()->getType())
|
|
<< "'";
|
|
for (auto arg : inferredArgs) {
|
|
remark << arg;
|
|
}
|
|
return remark;
|
|
});
|
|
}
|
|
|
|
void AssemblyVisionRemarkGeneratorInstructionVisitor::visitRetainValueInst(
|
|
RetainValueInst *rvi) {
|
|
ORE.emit([&]() {
|
|
using namespace OptRemark;
|
|
SmallVector<Argument, 8> inferredArgs;
|
|
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note,
|
|
rvi->getOperand(), inferredArgs);
|
|
(void)foundArgs;
|
|
// Retains begin a lifetime scope, so we infer scan forwards.
|
|
auto remark =
|
|
RemarkMissed("memory", *rvi,
|
|
SourceLocInferenceBehavior::ForwardScanAlwaysInfer)
|
|
<< "retain of type '" << NV("ValueType", rvi->getOperand()->getType())
|
|
<< "'";
|
|
for (auto arg : inferredArgs) {
|
|
remark << arg;
|
|
}
|
|
return remark;
|
|
});
|
|
}
|
|
|
|
void AssemblyVisionRemarkGeneratorInstructionVisitor::visitReleaseValueInst(
|
|
ReleaseValueInst *rvi) {
|
|
ORE.emit([&]() {
|
|
using namespace OptRemark;
|
|
SmallVector<Argument, 8> inferredArgs;
|
|
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note,
|
|
rvi->getOperand(), inferredArgs);
|
|
(void)foundArgs;
|
|
|
|
// Releases end a lifetime scope so we infer scan backward.
|
|
auto remark =
|
|
RemarkMissed("memory", *rvi,
|
|
SourceLocInferenceBehavior::BackwardThenForwardAlwaysInfer)
|
|
<< "release of type '" << NV("ValueType", rvi->getOperand()->getType())
|
|
<< "'";
|
|
for (auto arg : inferredArgs) {
|
|
remark << arg;
|
|
}
|
|
return remark;
|
|
});
|
|
}
|
|
|
|
void AssemblyVisionRemarkGeneratorInstructionVisitor::visitAllocRefInstBase(
|
|
AllocRefInstBase *ari) {
|
|
if (ari->canAllocOnStack()) {
|
|
return ORE.emit([&]() {
|
|
using namespace OptRemark;
|
|
SmallVector<Argument, 8> inferredArgs;
|
|
bool foundArgs =
|
|
valueToDeclInferrer.infer(ArgumentKeyKind::Note, ari, inferredArgs);
|
|
(void)foundArgs;
|
|
auto resultRemark =
|
|
RemarkPassed("memory", *ari, SourceLocInferenceBehavior::ForwardScan)
|
|
<< "stack allocated ref of type '" << NV("ValueType", ari->getType())
|
|
<< "'";
|
|
for (auto &arg : inferredArgs)
|
|
resultRemark << arg;
|
|
return resultRemark;
|
|
});
|
|
}
|
|
|
|
return ORE.emit([&]() {
|
|
using namespace OptRemark;
|
|
SmallVector<Argument, 8> inferredArgs;
|
|
bool foundArgs =
|
|
valueToDeclInferrer.infer(ArgumentKeyKind::Note, ari, inferredArgs);
|
|
(void)foundArgs;
|
|
|
|
auto resultRemark =
|
|
RemarkMissed("memory", *ari, SourceLocInferenceBehavior::ForwardScan)
|
|
<< "heap allocated ref of type '" << NV("ValueType", ari->getType())
|
|
<< "'";
|
|
for (auto &arg : inferredArgs)
|
|
resultRemark << arg;
|
|
return resultRemark;
|
|
});
|
|
}
|
|
|
|
|
|
void AssemblyVisionRemarkGeneratorInstructionVisitor::visitAllocRefInst(
|
|
AllocRefInst *ari) {
|
|
visitAllocRefInstBase(ari);
|
|
}
|
|
|
|
void AssemblyVisionRemarkGeneratorInstructionVisitor::visitAllocRefDynamicInst(
|
|
AllocRefDynamicInst *ari) {
|
|
visitAllocRefInstBase(ari);
|
|
}
|
|
|
|
void AssemblyVisionRemarkGeneratorInstructionVisitor::visitAllocBoxInst(
|
|
AllocBoxInst *abi) {
|
|
return ORE.emit([&]() {
|
|
using namespace OptRemark;
|
|
SmallVector<Argument, 8> inferredArgs;
|
|
bool foundArgs =
|
|
valueToDeclInferrer.infer(ArgumentKeyKind::Note, abi, inferredArgs);
|
|
(void)foundArgs;
|
|
|
|
auto resultRemark =
|
|
RemarkMissed("memory", *abi, SourceLocInferenceBehavior::ForwardScan)
|
|
<< "heap allocated box of type '" << NV("ValueType", abi->getType())
|
|
<< "'";
|
|
for (auto &arg : inferredArgs)
|
|
resultRemark << arg;
|
|
return resultRemark;
|
|
});
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Top Level Entrypoint
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
namespace {
|
|
|
|
class AssemblyVisionRemarkGenerator : public SILFunctionTransform {
|
|
~AssemblyVisionRemarkGenerator() override {}
|
|
|
|
bool isOptRemarksEnabled() {
|
|
auto *fn = getFunction();
|
|
// TODO: Put this on LangOpts as a helper.
|
|
auto &langOpts = fn->getASTContext().LangOpts;
|
|
|
|
// If we are supposed to emit remarks, always emit.
|
|
if (bool(langOpts.OptimizationRemarkMissedPattern) ||
|
|
bool(langOpts.OptimizationRemarkPassedPattern) ||
|
|
fn->getModule().getSILRemarkStreamer())
|
|
return true;
|
|
|
|
// Otherwise, first check if our function has a force emit opt remark prefix
|
|
// semantics tag.
|
|
if (fn->hasSemanticsAttrThatStartsWith(
|
|
semantics::FORCE_EMIT_OPT_REMARK_PREFIX))
|
|
return true;
|
|
|
|
// Otherwise, check if we have a self parameter that is a nominal type that
|
|
// is marked with the @_assemblyVision attribute.
|
|
if (fn->hasSelfParam()) {
|
|
if (auto *nomType = fn->getSelfArgument()
|
|
->getType()
|
|
.getNominalOrBoundGenericNominal()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Checking for remark on: "
|
|
<< nomType->getName().get() << "\n");
|
|
if (nomType->shouldEmitAssemblyVisionRemarksOnMethods()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Success! Will emit remarks!!\n");
|
|
return true;
|
|
}
|
|
LLVM_DEBUG(llvm::dbgs() << "Fail! No remarks will be emitted!!\n");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// The entry point to the transformation.
|
|
void run() override {
|
|
if (!isOptRemarksEnabled())
|
|
return;
|
|
|
|
auto *fn = getFunction();
|
|
|
|
// Skip top level implicit functions and top level autogenerated functions,
|
|
// unless we were asked by the user to emit them.
|
|
if (!ForceVisitImplicitAutogeneratedFunctions) {
|
|
// Skip implicit functions generated by Sema.
|
|
if (auto *ctx = fn->getDeclContext()) {
|
|
if (auto *decl = ctx->getAsDecl()) {
|
|
if (decl->isImplicit()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Skipping implicit decl function: "
|
|
<< fn->getName() << "\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip autogenerated functions generated by SILGen.
|
|
if (auto loc = fn->getDebugScope()->getLoc()) {
|
|
if (loc.isAutoGenerated()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Skipping autogenerated function: "
|
|
<< fn->getName() << "\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Visiting: " << fn->getName() << "\n");
|
|
auto &rcfi = *getAnalysis<RCIdentityAnalysis>()->get(fn);
|
|
AssemblyVisionRemarkGeneratorInstructionVisitor visitor(*fn, rcfi);
|
|
for (auto &block : *fn) {
|
|
for (auto &inst : block) {
|
|
visitor.visit(&inst);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
SILTransform *swift::createAssemblyVisionRemarkGenerator() {
|
|
return new AssemblyVisionRemarkGenerator();
|
|
}
|