Skip to content

[opt-remark] Add @_semantics("optremark{.$SIL_PASS_NAME}") that force opt remarks on functions. #33126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions include/swift/AST/SemanticAttrs.def
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,16 @@ SEMANTICS_ATTR(CONVERT_TO_OBJECTIVE_C, "convertToObjectiveC")

SEMANTICS_ATTR(KEYPATH_KVC_KEY_PATH_STRING, "keypath.kvcKeyPathString")

/// The prefix used to force opt-remarks to be emitted in a specific function.
///
/// If used just by itself "optremark", it is assumed that /all/ opt remarks
/// should be emitted. Otherwise, one can add a suffix after a '.' that
/// specifies a pass to emit opt-remarks from. So for instance to get just
/// information from 'sil-opt-remark-gen', one would write:
/// "optremark.sil-opt-remark-gen". One can add as many as one wishes. Keep in
/// mind that if the function itself is inlined, one will lose the optremark so
/// consider inlining where to put these.
SEMANTICS_ATTR(FORCE_EMIT_OPT_REMARK_PREFIX, "optremark")

#undef SEMANTICS_ATTR

1 change: 1 addition & 0 deletions include/swift/SIL/OptimizationRemark.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#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"
Expand Down
40 changes: 33 additions & 7 deletions lib/SIL/Utils/OptimizationRemark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
#include "swift/SIL/OptimizationRemark.h"
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/AST/SemanticAttrs.h"
#include "swift/Demangling/Demangler.h"
#include "swift/SIL/DebugUtils.h"
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/Projection.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SIL/SILRemarkStreamer.h"
Expand Down Expand Up @@ -108,16 +110,39 @@ std::string Remark<DerivedT>::getDebugMsg() const {
return stream.str();
}

static bool hasForceEmitSemanticAttr(SILFunction &fn, StringRef passName) {
return llvm::any_of(fn.getSemanticsAttrs(), [&](const std::string &str) {
auto ref = StringRef(str);

// First try to chomp the prefix.
if (!ref.consume_front(semantics::FORCE_EMIT_OPT_REMARK_PREFIX))
return false;

// Then see if we only have the prefix. Then always return true the user
// wants /all/ remarks.
if (ref.empty())
return true;

// Otherwise, lets try to chomp the '.' and then the passName.
if (!ref.consume_front(".") || !ref.consume_front(passName))
return false;

return ref.empty();
});
}

Emitter::Emitter(StringRef passName, SILFunction &fn)
: fn(fn), passName(passName),
passedEnabled(
fn.getASTContext().LangOpts.OptimizationRemarkPassedPattern &&
fn.getASTContext().LangOpts.OptimizationRemarkPassedPattern->match(
passName)),
hasForceEmitSemanticAttr(fn, passName) ||
(fn.getASTContext().LangOpts.OptimizationRemarkPassedPattern &&
fn.getASTContext().LangOpts.OptimizationRemarkPassedPattern->match(
passName))),
missedEnabled(
fn.getASTContext().LangOpts.OptimizationRemarkMissedPattern &&
fn.getASTContext().LangOpts.OptimizationRemarkMissedPattern->match(
passName)) {}
hasForceEmitSemanticAttr(fn, passName) ||
(fn.getASTContext().LangOpts.OptimizationRemarkMissedPattern &&
fn.getASTContext().LangOpts.OptimizationRemarkMissedPattern->match(
passName))) {}

/// The user has passed us an instruction that for some reason has a source loc
/// that can not be used. Search down the current block for an instruction with
Expand Down Expand Up @@ -216,7 +241,8 @@ static void emitRemark(SILFunction &fn, const Remark<RemarkT> &remark,
// If diagnostics are enabled, first emit the main diagnostic and then loop
// through our arguments and allow the arguments to add additional diagnostics
// if they want.
if (!diagEnabled)
if (!diagEnabled && !fn.hasSemanticsAttrThatStartsWith(
semantics::FORCE_EMIT_OPT_REMARK_PREFIX))
return;

auto &de = module.getASTContext().Diags;
Expand Down
143 changes: 76 additions & 67 deletions lib/SILOptimizer/Transforms/OptRemarkGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@

#define DEBUG_TYPE "sil-opt-remark-gen"

#include "swift/AST/SemanticAttrs.h"
#include "swift/SIL/MemAccessUtils.h"
#include "swift/SIL/OptimizationRemark.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/raw_ostream.h"

using namespace swift;

Expand All @@ -42,14 +45,18 @@ struct ValueToDeclInferrer {
using Argument = OptRemark::Argument;
using ArgumentKeyKind = OptRemark::ArgumentKeyKind;

bool infer(ArgumentKeyKind keyKind, StringRef msg, SILValue value,
bool infer(ArgumentKeyKind keyKind, SILValue value,
function_ref<bool(Argument)> funcPassedInferedArgs);
};

} // anonymous namespace

static void printNote(llvm::raw_string_ostream &stream, const ValueDecl *decl) {
stream << "of '" << decl->getBaseName() << "'";
}

bool ValueToDeclInferrer::infer(
ArgumentKeyKind keyKind, StringRef msg, SILValue value,
ArgumentKeyKind keyKind, SILValue value,
function_ref<bool(Argument)> funcPassedInferedArgs) {

// This is a linear IR traversal using a 'falling while loop'. That means
Expand All @@ -60,15 +67,27 @@ bool ValueToDeclInferrer::infer(
while (true) {
// First check for "identified values" like arguments and global_addr.
if (auto *arg = dyn_cast<SILArgument>(value))
if (auto *decl = arg->getDecl())
if (auto *decl = arg->getDecl()) {
std::string msg;
{
llvm::raw_string_ostream stream(msg);
printNote(stream, decl);
}
return funcPassedInferedArgs(
Argument({keyKind, "InferredValue"}, msg, decl));
Argument({keyKind, "InferredValue"}, std::move(msg), decl));
}

if (auto *ga = dyn_cast<GlobalAddrInst>(value))
if (auto *decl = ga->getReferencedGlobal()->getDecl())
if (auto *decl = ga->getReferencedGlobal()->getDecl()) {
std::string msg;
{
llvm::raw_string_ostream stream(msg);
printNote(stream, decl);
}
if (!funcPassedInferedArgs(
Argument({keyKind, "InferredValue"}, msg, decl)))
Argument({keyKind, "InferredValue"}, std::move(msg), decl)))
return false;
}

// Then visit our users and see if we can find a debug_value that provides
// us with a decl we can use to construct an argument.
Expand All @@ -78,15 +97,23 @@ bool ValueToDeclInferrer::infer(
continue;

if (auto *dvi = dyn_cast<DebugValueInst>(use->getUser())) {
if (auto *decl = dvi->getDecl())
if (auto *decl = dvi->getDecl()) {
std::string msg;
{
llvm::raw_string_ostream stream(msg);
printNote(stream, decl);
}
if (!funcPassedInferedArgs(
Argument({keyKind, "InferredValue"}, msg, decl)))
Argument({keyKind, "InferredValue"}, std::move(msg), decl)))
return false;
}
}
}

// At this point, we could not infer any argument. See if we can look
// through loads, geps to construct a ProjectionPath.
// through loads.
//
// TODO: Add GEPs to construct a ProjectionPath.

// Finally, see if we can look through a load...
if (auto *li = dyn_cast<LoadInst>(value)) {
Expand Down Expand Up @@ -135,24 +162,19 @@ void OptRemarkGeneratorInstructionVisitor::visitStrongRetainInst(
using namespace OptRemark;
SILValue root = rcfi.getRCIdentityRoot(sri->getOperand());
SmallVector<Argument, 8> inferredArgs;
bool foundArgs = valueToDeclInferrer.infer(
ArgumentKeyKind::Note, "on value:", root, [&](Argument arg) {
inferredArgs.push_back(arg);
return true;
});
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note, root,
[&](Argument arg) {
inferredArgs.push_back(arg);
return true;
});
(void)foundArgs;

// Retains begin a lifetime scope so we infer scan forward.
auto remark = RemarkMissed("memory-management", *sri,
auto remark = RemarkMissed("memory", *sri,
SourceLocInferenceBehavior::ForwardScanOnly)
<< "Found retain:";
if (inferredArgs.empty()) {
remark << Argument({ArgumentKeyKind::ParentLocNote, "InferValueFailure"},
"Unable to infer any values being retained.");
} else {
for (auto arg : inferredArgs) {
remark << arg;
}
<< "retain";
for (auto arg : inferredArgs) {
remark << arg;
}
return remark;
});
Expand All @@ -165,22 +187,18 @@ void OptRemarkGeneratorInstructionVisitor::visitStrongReleaseInst(
// Releases end a lifetime scope so we infer scan backward.
SILValue root = rcfi.getRCIdentityRoot(sri->getOperand());
SmallVector<Argument, 8> inferredArgs;
bool foundArgs = valueToDeclInferrer.infer(
ArgumentKeyKind::Note, "on value:", root, [&](Argument arg) {
inferredArgs.push_back(arg);
return true;
});
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note, root,
[&](Argument arg) {
inferredArgs.push_back(arg);
return true;
});
(void)foundArgs;
auto remark = RemarkMissed("memory-management", *sri,

auto remark = RemarkMissed("memory", *sri,
SourceLocInferenceBehavior::BackwardScanOnly)
<< "Found release:";
if (inferredArgs.empty()) {
remark << Argument({ArgumentKeyKind::ParentLocNote, "InferValueFailure"},
"Unable to infer any values being released.");
} else {
for (auto arg : inferredArgs) {
remark << arg;
}
<< "release";
for (auto arg : inferredArgs) {
remark << arg;
}
return remark;
});
Expand All @@ -192,24 +210,19 @@ void OptRemarkGeneratorInstructionVisitor::visitRetainValueInst(
using namespace OptRemark;
SILValue root = rcfi.getRCIdentityRoot(rvi->getOperand());
SmallVector<Argument, 8> inferredArgs;
bool foundArgs = valueToDeclInferrer.infer(
ArgumentKeyKind::Note, "on value:", root, [&](Argument arg) {
inferredArgs.push_back(arg);
return true;
});
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note, root,
[&](Argument arg) {
inferredArgs.push_back(arg);
return true;
});
(void)foundArgs;

// Retains begin a lifetime scope, so we infer scan forwards.
auto remark = RemarkMissed("memory-management", *rvi,
auto remark = RemarkMissed("memory", *rvi,
SourceLocInferenceBehavior::ForwardScanOnly)
<< "Found retain:";
if (inferredArgs.empty()) {
remark << Argument({ArgumentKeyKind::ParentLocNote, "InferValueFailure"},
"Unable to infer any values being retained.");
} else {
for (auto arg : inferredArgs) {
remark << arg;
}
<< "retain";
for (auto arg : inferredArgs) {
remark << arg;
}
return remark;
});
Expand All @@ -221,26 +234,20 @@ void OptRemarkGeneratorInstructionVisitor::visitReleaseValueInst(
using namespace OptRemark;
SILValue root = rcfi.getRCIdentityRoot(rvi->getOperand());
SmallVector<Argument, 8> inferredArgs;
bool foundArgs = valueToDeclInferrer.infer(
ArgumentKeyKind::Note, "on value:", root, [&](Argument arg) {
inferredArgs.push_back(arg);
return true;
});
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note, root,
[&](Argument arg) {
inferredArgs.push_back(arg);
return true;
});
(void)foundArgs;

// Releases end a lifetime scope so we infer scan backward.
auto remark = RemarkMissed("memory-management", *rvi,
auto remark = RemarkMissed("memory", *rvi,
SourceLocInferenceBehavior::BackwardScanOnly)
<< "Found release:";
if (inferredArgs.empty()) {
remark << Argument({ArgumentKeyKind::ParentLocNote, "InferValueFailure"},
"Unable to infer any values being released.");
} else {
for (auto arg : inferredArgs) {
remark << arg;
}
<< "release";
for (auto arg : inferredArgs) {
remark << arg;
}

return remark;
});
}
Expand All @@ -261,7 +268,9 @@ class OptRemarkGenerator : public SILFunctionTransform {

return bool(langOpts.OptimizationRemarkMissedPattern) ||
bool(langOpts.OptimizationRemarkPassedPattern) ||
fn->getModule().getSILRemarkStreamer();
fn->getModule().getSILRemarkStreamer() ||
fn->hasSemanticsAttrThatStartsWith(
semantics::FORCE_EMIT_OPT_REMARK_PREFIX);
}

/// The entry point to the transformation.
Expand Down
28 changes: 25 additions & 3 deletions lib/SILOptimizer/Transforms/PerformanceInliner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,13 @@ bool SILPerformanceInliner::isProfitableToInline(
// It is always OK to inline a simple call.
// TODO: May be consider also the size of the callee?
if (isPureCall(AI, SEA)) {
OptRemark::Emitter::emitOrDebug(DEBUG_TYPE, &ORE, [&]() {
using namespace OptRemark;
return RemarkPassed("Inline", *AI.getInstruction())
<< "Pure call. Always profitable to inline "
<< NV("Callee", Callee);
});

LLVM_DEBUG(dumpCaller(AI.getFunction());
llvm::dbgs() << " pure-call decision " << Callee->getName()
<< '\n');
Expand Down Expand Up @@ -487,9 +494,24 @@ bool SILPerformanceInliner::isProfitableToInline(
auto *bb = AI.getInstruction()->getParent();
auto bbIt = BBToWeightMap.find(bb);
if (bbIt != BBToWeightMap.end()) {
return profileBasedDecision(AI, Benefit, Callee, CalleeCost,
NumCallerBlocks, bbIt);
if (profileBasedDecision(AI, Benefit, Callee, CalleeCost, NumCallerBlocks,
bbIt)) {
OptRemark::Emitter::emitOrDebug(DEBUG_TYPE, &ORE, [&]() {
using namespace OptRemark;
return RemarkPassed("Inline", *AI.getInstruction())
<< "Profitable due to provided profile";
});
return true;
}

OptRemark::Emitter::emitOrDebug(DEBUG_TYPE, &ORE, [&]() {
using namespace OptRemark;
return RemarkMissed("Inline", *AI.getInstruction())
<< "Not profitable due to provided profile";
});
return false;
}

if (isClassMethodAtOsize && Benefit > OSizeClassMethodBenefit) {
Benefit = OSizeClassMethodBenefit;
}
Expand All @@ -498,7 +520,7 @@ bool SILPerformanceInliner::isProfitableToInline(
if (CalleeCost > Benefit) {
OptRemark::Emitter::emitOrDebug(DEBUG_TYPE, &ORE, [&]() {
using namespace OptRemark;
return RemarkMissed("NoInlinedCost", *AI.getInstruction())
return RemarkMissed("Inline", *AI.getInstruction())
<< "Not profitable to inline function " << NV("Callee", Callee)
<< " (cost = " << NV("Cost", CalleeCost)
<< ", benefit = " << NV("Benefit", Benefit) << ")";
Expand Down
Loading